/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*-------------------------------------------------------------------------
 *
 * analyze.c
 *	  transform the raw parse tree into a query tree
 *
 * For optimizable statements, we are careful to obtain a suitable lock on
 * each referenced table, and other modules of the backend preserve or
 * re-obtain these locks before depending on the results.  It is therefore
 * okay to do significant semantic analysis of these statements.  For
 * utility commands, no locks are obtained here (and if they were, we could
 * not be sure we'd still have them at execution).  Hence the general rule
 * for utility commands is to just dump them into a Query node untransformed.
 * DECLARE CURSOR and EXPLAIN are exceptions because they contain
 * optimizable statements.
 *
 *
 * Portions Copyright (c) 2005-2010, Greenplum inc
 * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *	$PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.353.2.1 2007/06/20
 *18:21:08 tgl Exp $
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"
#include "port.h"

#include <uuid/uuid.h>

#include "access/heapam.h"
#include "access/reloptions.h"
#include "access/plugstorage.h"
#include "catalog/catquery.h"
#include "catalog/gp_policy.h"
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/pg_compression.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_exttable.h"
#include "catalog/pg_partition.h"
#include "catalog/pg_partition_rule.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_type.h"
#include "catalog/pg_type_encoding.h"
#include "cdb/cdbpartition.h"
#include "cdb/cdbparquetstoragewrite.h"
#include "cdb/cdbdatalocality.h"
#include "catalog/skylon_vlabel.h"
#include "catalog/skylon_vlabel_attribute.h"
#include "catalog/skylon_elabel.h"
#include "catalog/skylon_elabel_attribute.h"
#include "catalog/skylon_graph.h"
#include "catalog/skylon_graph_vlabel.h"
#include "catalog/skylon_graph_elabel.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
#include "commands/prepare.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "nodes/nodes.h"
#include "nodes/pg_list.h"
#include "nodes/value.h"
#include "optimizer/clauses.h"
#include "optimizer/plancat.h"
#include "optimizer/tlist.h"
#include "optimizer/var.h"
#include "optimizer/planmain.h"
#include "parser/analyze.h"
#include "parser/gramparse.h"
#include "parser/parse_agg.h"
#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_expr.h"
#include "parser/parse_func.h"
#include "parser/parse_oper.h"
#include "parser/parse_relation.h"
#include "parser/parse_target.h"
#include "parser/parse_type.h"
#include "parser/parse_utilcmd.h"
#include "parser/parse_cte.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
#include "utils/builtins.h"
#include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/palloc.h"
#include "utils/syscache.h"
#include "utils/uri.h"

#include "cdb/cdbappendonlyam.h"
#include "cdb/cdbvars.h"
#include "cdb/cdbcat.h"
#include "cdb/cdbhash.h"
#include "cdb/cdbsreh.h"

/* temporary rule to control whether we generate RULEs or not -- for testing */
bool enable_partition_rules = false;

/* State shared by transformCreateSchemaStmt and its subroutines */
typedef struct {
  const char *stmtType; /* "CREATE SCHEMA" or "ALTER SCHEMA" */
  char *schemaname;     /* name of schema */
  char *authid;         /* owner of schema */
  List *sequences;      /* CREATE SEQUENCE items */
  List *tables;         /* CREATE TABLE items */
  List *views;          /* CREATE VIEW items */
  List *indexes;        /* CREATE INDEX items */
  List *triggers;       /* CREATE TRIGGER items */
  List *grants;         /* GRANT items */
  List *fwconstraints;  /* Forward referencing FOREIGN KEY constraints */
  List *alters;         /* Generated ALTER items (from the above) */
  List *ixconstraints;  /* index-creating constraints */
  List *blist;          /* "before list" of things to do before
                                         * creating the schema */
  List *alist;          /* "after list" of things to do after creating
                                         * the schema */
} CreateSchemaStmtContext;

typedef struct {
  Oid *paramTypes;
  int numParams;
} check_parameter_resolution_context;

/* Context for transformGroupedWindows() which mutates components
 * of a query that mixes windowing and aggregation or grouping.  It
 * accumulates context for eventual construction of a subquery (the
 * grouping query) during mutation of components of the outer query
 * (the windowing query).
 */
typedef struct {
  List *subtlist;       /* target list for subquery */
  List *subgroupClause; /* group clause for subquery */
  List *windowClause;   /* window clause for outer query*/

  /* Scratch area for init_grouped_window context and map_sgr_mutator.
   */
  Index *sgr_map;

  /* Scratch area for grouped_window_mutator and var_for_gw_expr.
   */
  List *subrtable;
  int call_depth;
  TargetEntry *tle;
} grouped_window_ctx;

typedef struct {
  ParseState *pstate;
  List *cols;
} part_col_cxt;

/* state structures for validing parsed partition specifications */
typedef struct {
  ParseState *pstate;
  CreateStmtContext *cxt;
  CreateStmtBase *stmt;
  PartitionBy *pBy;
  PartitionElem *pElem;
  PartitionElem *prevElem;
  Node *spec;
  int partNumber;
  char *namBuf;
  char *at_depth;
  bool prevHadName;
  int prevStartEnd;
  List *allRangeVals;
  List *allListVals;
} partValidationState;

typedef struct {
  ParseState *pstate;
  int location;
} range_partition_ctx;

static List *do_parse_analyze(Node *parseTree, ParseState *pstate);
static void parse_analyze_error_callback(void *parsestate); /*CDB*/
static Query *transformStmt(ParseState *pstate, Node *stmt,
                            List **extras_before, List **extras_after);
static Query *transformViewStmt(ParseState *pstate, ViewStmt *stmt,
                                List **extras_before, List **extras_after);
static Query *transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt);
static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt,
                                  List **extras_before, List **extras_after);
static List *transformInsertRow(ParseState *pstate, List *exprlist,
                                List *stmtcols, List *icolumns, List *attrnos);

/*
 * MPP-2506 [insert/update/delete] RETURNING clause not supported:
 *   We have problems processing the returning clause, so for now we have
 *   simply removed it and replaced it with an error message.
 */
#define MPP_RETURNING_NOT_SUPPORTED
#ifndef MPP_RETURNING_NOT_SUPPORTED
static List *transformReturningList(ParseState *pstate, List *returningList);
#endif

static Query *transformIndexStmt(ParseState *pstate, IndexStmt *stmt,
                                 List **extras_before, List **extras_after);
static Query *transformRuleStmt(ParseState *query, RuleStmt *stmt,
                                List **extras_before, List **extras_after);
static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt);
static Query *transformValuesClause(ParseState *pstate, SelectStmt *stmt);
static Query *transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt);
static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt);
static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
static Query *transformDeclareCursorStmt(ParseState *pstate,
                                         DeclareCursorStmt *stmt);
static bool isSimplyUpdatableQuery(Query *query);
static Query *transformPrepareStmt(ParseState *pstate, PrepareStmt *stmt);
static Query *transformExecuteStmt(ParseState *pstate, ExecuteStmt *stmt);
static Query *transformCreateExternalStmt(ParseState *pstate,
                                          CreateExternalStmt *stmt,
                                          List **extras_before,
                                          List **extras_after);
static Query *transformCreateVlabelStmt(ParseState *pstate,
                                          CreateVlabelStmt *stmt,
                                          List **extras_before,
                                          List **extras_after);
static Query *transformCreateElabelStmt(ParseState *pstate,
                                          CreateElabelStmt *stmt,
                                          List **extras_before,
                                          List **extras_after);
static Query *transformCreateGraphStmt(ParseState *pstate,
                                          CreateGraphStmt *stmt,
                                          List **extras_before,
                                          List **extras_after);
static Query *transformCreateForeignStmt(ParseState *pstate,
                                         CreateForeignStmt *stmt,
                                         List **extras_before,
                                         List **extras_after);
static Query *transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt,
                                      List **extras_before,
                                      List **extras_after);
static void transformColumnDefinition(ParseState *pstate,
                                      CreateStmtContext *cxt,
                                      ColumnDef *column);
static void transformTableConstraint(ParseState *pstate, CreateStmtContext *cxt,
                                     Constraint *constraint);
static void transformExtTableConstraint(ParseState *pstate,
                                        CreateStmtContext *cxt,
                                        Constraint *constraint);

static void transformETDistributedBy(ParseState *pstate, CreateStmtContext *cxt,
                                     List **distributedBy, GpPolicy **policyp,
                                     List *options, List *likeDistributedBy,
                                     bool bQuiet, bool iswritable,
                                     bool onmaster);
static void transformDistributedBy(ParseState *pstate, CreateStmtContext *cxt,
                                   List *distributedBy, GpPolicy **policy,
                                   List *options, List *likeDistributedBy,
                                   bool bQuiet);
static void transformPartitionBy(ParseState *pstate, CreateStmtContext *cxt,
                                 CreateStmtBase *stmt, Node *partitionBy,
                                 GpPolicy *policy);

static void transformFKConstraints(ParseState *pstate, CreateStmtContext *cxt,
                                   bool skipValidation, bool isAddConstraint);
static void applyColumnNames(List *dst, List *src);
static bool isSetopLeaf(SelectStmt *stmt);
static void collectSetopTypes(ParseState *pstate, SelectStmt *stmt,
                              List **types, List **typmods);
static void getSetColTypes(ParseState *pstate, Node *node, List **colTypes,
                           List **colTypmods);
static void transformLockingClause(Query *qry, LockingClause *lc);
static void transformConstraintAttrs(List *constraintList);
static void transformColumnType(ParseState *pstate, ColumnDef *column);
static void release_pstate_resources(ParseState *pstate);
static FromExpr *makeFromExpr(List *fromlist, Node *quals);
static bool check_parameter_resolution_walker(
    Node *node, check_parameter_resolution_context *context);

static void setQryDistributionPolicy(SelectStmt *stmt, Query *qry);
static List *getLikeDistributionPolicy(InhRelation *e);

static void transformSingleRowErrorHandling(ParseState *pstate,
                                            CreateStmtContext *cxt,
                                            SingleRowErrorDesc *sreh);

static Query *transformGroupedWindows(Query *qry);
static void init_grouped_window_context(grouped_window_ctx *ctx, Query *qry);
static Var *var_for_gw_expr(grouped_window_ctx *ctx, Node *expr, bool force);
static void discard_grouped_window_context(grouped_window_ctx *ctx);
static Node *map_sgr_mutator(Node *node, void *context);
static Node *grouped_window_mutator(Node *node, void *context);
static Alias *make_replacement_alias(Query *qry, const char *aname);
static char *generate_positional_name(AttrNumber attrno);
static List *generate_alternate_vars(Var *var, grouped_window_ctx *ctx);

static List *fillin_encoding(List *list);

static int deparse_partition_rule(Node *pNode, char *outbuf, size_t outsize);

static int partition_range_compare(ParseState *pstate, CreateStmtContext *cxt,
                                   CreateStmtBase *stmt, PartitionBy *pBy,
                                   char *at_depth, int partNumber,
                                   char *compare_op, PartitionRangeItem *pRI1,
                                   PartitionRangeItem *pRI2);

static int partition_range_every(ParseState *pstate, PartitionBy *partitionBy,
                                 List *coltypes, char *at_depth,
                                 partValidationState *vstate);
static Datum eval_basic_opexpr(ParseState *pstate, List *oprname, Node *leftarg,
                               Node *rightarg, bool *typbyval, int16 *typlen,
                               Oid *restypid, int location);

static Node *make_prule_catalog(ParseState *pstate, CreateStmtContext *cxt,
                                CreateStmtBase *stmt, Node *partitionBy,
                                PartitionElem *pElem, char *at_depth,
                                char *child_name_str, char *exprBuf,
                                Node *pWhere);

static Node *make_prule_rulestmt(ParseState *pstate, CreateStmtContext *cxt,
                                 CreateStmtBase *stmt, Node *partitionBy,
                                 PartitionElem *pElem, char *at_depth,
                                 char *child_name_str, char *exprBuf,
                                 Node *pWhere);

static List *transformAttributeEncoding(List *stenc, CreateStmt *stmt,
                                        CreateStmtContext cxt);

extern char *graphVertexTableName(char *gname,char *vname);

extern char *graphEdgeTableName(char *gname,char *ename);

extern bool parseAndTransformAsGraph(ParseState *pstate, RangeVar *rangeVar);

char *getDefaultFilespace();

/*
 * parse_analyze
 *		Analyze a raw parse tree and transform it to Query form.
 *
 * If available, pass the source text from which the raw parse tree was
 * generated; it's OK to pass NULL if this is not available.
 *
 * Optionally, information about $n parameter types can be supplied.
 * References to $n indexes not defined by paramTypes[] are disallowed.
 *
 * The result is a List of Query nodes (we need a list since some commands
 * produce multiple Queries).  Optimizable statements require considerable
 * transformation, while many utility-type statements are simply hung off
 * a dummy CMD_UTILITY Query node.
 */
List *parse_analyze(Node *parseTree, const char *sourceText, Oid *paramTypes,
                    int numParams) {
  ParseState *pstate = make_parsestate(NULL);
  List *result;

  pstate->p_sourcetext = sourceText;
  pstate->p_paramtypes = paramTypes;
  pstate->p_numparams = numParams;
  pstate->p_variableparams = false;

  result = do_parse_analyze(parseTree, pstate);

  free_parsestate(&pstate);

  return result;
}

/*
 * parse_analyze_varparams
 *
 * This variant is used when it's okay to deduce information about $n
 * symbol datatypes from context.  The passed-in paramTypes[] array can
 * be modified or enlarged (via repalloc).
 */
List *parse_analyze_varparams(Node *parseTree, const char *sourceText,
                              Oid **paramTypes, int *numParams) {
  ParseState *pstate = make_parsestate(NULL);
  List *result;

  pstate->p_sourcetext = sourceText;
  pstate->p_paramtypes = *paramTypes;
  pstate->p_numparams = *numParams;
  pstate->p_variableparams = true;

  result = do_parse_analyze(parseTree, pstate);

  *paramTypes = pstate->p_paramtypes;
  *numParams = pstate->p_numparams;

  free_parsestate(&pstate);

  /* make sure all is well with parameter types */
  if (*numParams > 0) {
    check_parameter_resolution_context context;

    context.paramTypes = *paramTypes;
    context.numParams = *numParams;
    check_parameter_resolution_walker((Node *)result, &context);
  }

  return result;
}

/*
 * parse_sub_analyze
 *		Entry point for recursively analyzing a sub-statement.
 */
List *parse_sub_analyze(Node *parseTree, ParseState *parentParseState) {
  ParseState *pstate = make_parsestate(parentParseState);
  List *result;

  result = do_parse_analyze(parseTree, pstate);

  free_parsestate(&pstate);

  return result;
}

static int alter_cmp(const void *a, const void *b) {
  Query *qa = *(Query **)a;
  Query *qb = *(Query **)b;
  AlterTableStmt *stmta = (AlterTableStmt *)qa->utilityStmt;
  AlterTableStmt *stmtb = (AlterTableStmt *)qb->utilityStmt;
  PartitionBy *pbya = NULL;
  PartitionBy *pbyb = NULL;
  int len1, len2;
  ListCell *lc;

  Assert(IsA(stmta, AlterTableStmt));
  Assert(IsA(stmtb, AlterTableStmt));

  foreach (lc, stmta->cmds) {
    AlterTableCmd *cmd = lfirst(lc);

    if (cmd->subtype == AT_PartAddInternal) {
      pbya = (PartitionBy *)cmd->def;
      break;
    }
  }

  foreach (lc, stmtb->cmds) {
    AlterTableCmd *cmd = lfirst(lc);

    if (cmd->subtype == AT_PartAddInternal) {
      pbyb = (PartitionBy *)cmd->def;
      break;
    }
  }

  if (pbya && pbyb) {
    if (pbya->partDepth < pbyb->partDepth)
      return -1;
    else if (pbya->partDepth > pbyb->partDepth)
      return 1;
    else
      return 0;
  }

  len1 = strlen(stmta->relation->relname);
  len2 = strlen(stmtb->relation->relname);

  if (len1 < len2)
    return -1;
  else if (len1 > len2)
    return 1;
  else
    /* same size */
    return strcmp(stmta->relation->relname, stmtb->relation->relname);
}

/*
 * do_parse_analyze
 *		Workhorse code shared by the above variants of parse_analyze.
 */
static List *do_parse_analyze(Node *parseTree, ParseState *pstate) {
  List *result = NIL;

  /* Lists to return extra commands from transformation */
  List *extras_before = NIL;
  List *extras_after = NIL;
  List *tmp = NIL;
  Query *query;
  ListCell *l;

  ErrorContextCallback errcontext;

  /* CDB: Request a callback in case ereport or elog is called. */
  errcontext.callback = parse_analyze_error_callback;
  errcontext.arg = pstate;
  errcontext.previous = error_context_stack;
  error_context_stack = &errcontext;

  query = transformStmt(pstate, parseTree, &extras_before, &extras_after);

  /* CDB: Pop error context callback stack. */
  error_context_stack = errcontext.previous;

  /* CDB: All breadcrumbs should have been popped. */
  Assert(!pstate->p_breadcrumb.pop);

  /* don't need to access result relation any more */
  release_pstate_resources(pstate);

  foreach (l, extras_before)
    result = list_concat(result, parse_sub_analyze(lfirst(l), pstate));

  result = lappend(result, query);

  foreach (l, extras_after)
    tmp = list_concat(tmp, parse_sub_analyze(lfirst(l), pstate));

  /*
   * If this is the top level query and it is a CreateStmt/CreateExternalStmt
   * and it has a partition by clause, reorder the expanded extras_after so
   * that AlterTable is able to build the partitioning hierarchy
   * better. The problem with the existing list is that for
   * subpartitioned tables, the subpartitions will be added to the
   * hierarchy before the root, which means we cannot get the parent
   * oid of rules.
   *
   * nefarious: special KeepMe case in cdbpartition.c:atpxPart_validate_spec
   */
  if (pstate->parentParseState == NULL && query->utilityStmt &&
      (IsA(query->utilityStmt, CreateStmt) &&
           ((CreateStmt *)query->utilityStmt)->base.partitionBy ||
       IsA(query->utilityStmt, CreateExternalStmt) &&
           ((CreateExternalStmt *)query->utilityStmt)->base.partitionBy)) {
    /*
     * We just break the statements into two lists: alter statements and
     * other statements.
     */
    List *alters = NIL;
    List *others = NIL;
    Query **stmts;
    int i = 0;
    int j;

    foreach (l, tmp) {
      Query *q = lfirst(l);

      Assert(IsA(q, Query));

      if (IsA(q->utilityStmt, AlterTableStmt))
        alters = lappend(alters, q);
      else
        others = lappend(others, q);
    }

    Assert(list_length(alters));

    /*
     * Now, sort the ALTER statements so that the deeper partition members
     * are processed last.
     */
    stmts = palloc(list_length(alters) * sizeof(Query *));
    foreach (l, alters)
      stmts[i++] = (Query *)lfirst(l);

    qsort(stmts, i, sizeof(void *), alter_cmp);

    list_free(alters);
    alters = NIL;
    for (j = 0; j < i; j++) {
      AlterTableStmt *n;
      alters = lappend(alters, stmts[j]);

      n = (AlterTableStmt *)((Query *)stmts[j])->utilityStmt;
    }
    result = list_concat(result, others);
    result = list_concat(result, alters);

  } else
    result = list_concat(result, tmp);

  /*
   * Make sure that only the original query is marked original. We have to
   * do this explicitly since recursive calls of do_parse_analyze will have
   * marked some of the added-on queries as "original".  Also mark only the
   * original query as allowed to set the command-result tag.
   */
  foreach (l, result) {
    Query *q = lfirst(l);

    if (q == query) {
      q->querySource = QSRC_ORIGINAL;
      q->canSetTag = true;
    } else {
      q->querySource = QSRC_PARSER;
      q->canSetTag = false;
    }
  }

  return result;
}

static void release_pstate_resources(ParseState *pstate) {
  if (pstate->p_target_relation != NULL)
    heap_close(pstate->p_target_relation, NoLock);
  pstate->p_target_relation = NULL;
  pstate->p_target_rangetblentry = NULL;
}

/*
 * parse_analyze_error_callback
 *
 * Called during elog/ereport to add context information to the error message.
 */
static void parse_analyze_error_callback(void *parsestate) {
  ParseState *pstate = (ParseState *)parsestate;
  ParseStateBreadCrumb *bc;
  int location = -1;

  /* No-op if errposition has already been set. */
  if (geterrposition() > 0) return;

  /* NOTICE messages don't need any extra baggage. */
  if (elog_getelevel() == NOTICE) return;

  /*
       * Backtrack through trail of breadcrumbs to find a node with location
       * info. A null node ptr tells us to keep quiet rather than give a
       * misleading pointer to a token which may be far from the actual problem.
   */
  for (bc = &pstate->p_breadcrumb; bc && bc->node; bc = bc->pop) {
    location = parse_expr_location((Expr *)bc->node);
    if (location >= 0) break;
  }

  /* Shush the parent query's error callback if we found a location or null */
  if (bc && pstate->parentParseState)
    pstate->parentParseState->p_breadcrumb.node = NULL;

  /* Report approximate offset of error from beginning of statement text. */
  if (location >= 0) parser_errposition(pstate, location);
} /* parse_analyze_error_callback */

/*
 * transformStmt -
 *	  transform a Parse tree into a Query tree.
 */
static Query *transformStmt(ParseState *pstate, Node *parseTree,
                            List **extras_before, List **extras_after) {
  Query *result = NULL;

  switch (nodeTag(parseTree)) {
    /*
     * Non-optimizable statements
     */
    case T_CreateStmt:
      result = transformCreateStmt(pstate, (CreateStmt *)parseTree,
                                   extras_before, extras_after);
      break;

    case T_CreateExternalStmt:
      result = transformCreateExternalStmt(
          pstate, (CreateExternalStmt *)parseTree, extras_before, extras_after);
      break;

    case T_CreateVlabelStmt:
      result = transformCreateVlabelStmt(
          pstate, (CreateVlabelStmt *)parseTree, extras_before, extras_after);
      break;

    case T_CreateElabelStmt:
      result = transformCreateElabelStmt(
          pstate, (CreateElabelStmt *)parseTree, extras_before, extras_after);
      break;

    case T_CreateGraphStmt:
      result = transformCreateGraphStmt(
                pstate, (CreateGraphStmt *)parseTree, extras_before, extras_after);
      break;

    case T_CreateForeignStmt:
      result = transformCreateForeignStmt(
          pstate, (CreateForeignStmt *)parseTree, extras_before, extras_after);
      break;

    case T_IndexStmt:
      result = transformIndexStmt(pstate, (IndexStmt *)parseTree, extras_before,
                                  extras_after);
      break;

    case T_RuleStmt:
      result = transformRuleStmt(pstate, (RuleStmt *)parseTree, extras_before,
                                 extras_after);
      break;

    case T_ViewStmt:
      result = transformViewStmt(pstate, (ViewStmt *)parseTree, extras_before,
                                 extras_after);
      break;

    case T_ExplainStmt: {
      ExplainStmt *n = (ExplainStmt *)parseTree;

      result = makeNode(Query);
      result->commandType = CMD_UTILITY;
      n->query =
          transformStmt(pstate, (Node *)n->query, extras_before, extras_after);
      result->utilityStmt = (Node *)parseTree;
    } break;

    case T_CopyStmt: {
      CopyStmt *n = (CopyStmt *)parseTree;

      /*
       * Check if we need to create an error table. If so, add it to the
       * before list.
       */
      if (n->sreh && ((SingleRowErrorDesc *)n->sreh)->errtable) {
        CreateStmtContext cxt;
        cxt.blist = NIL;
        cxt.alist = NIL;

        transformSingleRowErrorHandling(pstate, &cxt,
                                        (SingleRowErrorDesc *)n->sreh);
        *extras_before = list_concat(*extras_before, cxt.blist);
      }

      result = makeNode(Query);
      result->commandType = CMD_UTILITY;
      if (n->query)
        n->query = transformStmt(pstate, (Node *)n->query, extras_before,
                                 extras_after);
      result->utilityStmt = (Node *)parseTree;
    } break;

    case T_AlterTableStmt:
      result = transformAlterTableStmt(pstate, (AlterTableStmt *)parseTree,
                                       extras_before, extras_after);
      break;

    case T_PrepareStmt:
      result = transformPrepareStmt(pstate, (PrepareStmt *)parseTree);
      break;

    case T_ExecuteStmt:
      result = transformExecuteStmt(pstate, (ExecuteStmt *)parseTree);
      break;

    /*
     * Optimizable statements
     */
    case T_InsertStmt:
      result = transformInsertStmt(pstate, (InsertStmt *)parseTree,
                                   extras_before, extras_after);
      break;

    case T_DeleteStmt:
      result = transformDeleteStmt(pstate, (DeleteStmt *)parseTree);
      break;

    case T_UpdateStmt:
      result = transformUpdateStmt(pstate, (UpdateStmt *)parseTree);
      break;

    case T_SelectStmt: {
      SelectStmt *n = (SelectStmt *)parseTree;

      if (n->valuesLists)
        result = transformValuesClause(pstate, n);
      else if (n->op == SETOP_NONE)
        result = transformSelectStmt(pstate, n);
      else
        result = transformSetOperationStmt(pstate, n);
    }
    break;

    case T_DeclareCursorStmt:
      result =
          transformDeclareCursorStmt(pstate, (DeclareCursorStmt *)parseTree);
      break;

    default:

      /*
       * other statements don't require any transformation; just return
       * the original parsetree with a Query node plastered on top.
       */
      result = makeNode(Query);
      result->commandType = CMD_UTILITY;
      result->utilityStmt = (Node *)parseTree;
      break;
  }

  /* Mark as original query until we learn differently */
  result->querySource = QSRC_ORIGINAL;
  result->canSetTag = true;
  result->graphEntry = pstate->graphEntry;

  /*
   * Check that we did not produce too many resnos; at the very least we
   * cannot allow more than 2^16, since that would exceed the range of a
   * AttrNumber. It seems safest to use MaxTupleAttributeNumber.
   */
  if (pstate->p_next_resno - 1 > MaxTupleAttributeNumber)
    ereport(ERROR, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
                    errmsg("target lists can have at most %d entries",
                           MaxTupleAttributeNumber)));

  return result;
}

/*
 * analyze_requires_snapshot
 *              Returns true if a snapshot must be set before doing parse
 *analysis
 *              on the given raw parse tree.
 *
 * Classification here should match transformStmt(); but we also have to
 * allow a NULL input (for Parse/Bind of an empty query string).
 */
bool analyze_requires_snapshot(Node *parseTree) {
  bool result;

  if (parseTree == NULL) return false;

  switch (nodeTag(parseTree)) {
    /*
     * Optimizable statements
     */
    case T_InsertStmt:
    case T_DeleteStmt:
    case T_UpdateStmt:
    case T_SelectStmt:
      result = true;
      break;

    /*
     * Special cases
     */
    case T_DeclareCursorStmt:
      /* yes, because it's analyzed just like SELECT */
      result = true;
      break;

    case T_ExplainStmt:
      /* yes, because it's analyzed just like SELECT */
      result = true;
      break;

    default:
      /* other utility statements don't have any active parse analysis */
      result = false;
      break;
  }

  return result;
}

static Query *transformViewStmt(ParseState *pstate, ViewStmt *stmt,
                                List **extras_before, List **extras_after) {
  Query *result = makeNode(Query);

  result->commandType = CMD_UTILITY;
  result->utilityStmt = (Node *)stmt;

  stmt->query =
      transformStmt(pstate, (Node *)stmt->query, extras_before, extras_after);

  if (pstate->p_hasDynamicFunction) {
    ereport(ERROR, (errcode(ERRCODE_INDETERMINATE_DATATYPE),
                    errmsg(
                        "CREATE VIEW statements cannot include calls to "
                        "dynamically typed function")));
  }

  /*
   * If a list of column names was given, run through and insert these into
   * the actual query tree. - thomas 2000-03-08
   *
   * Outer loop is over targetlist to make it easier to skip junk targetlist
   * entries.
   */
  if (stmt->aliases != NIL) {
    ListCell *alist_item = list_head(stmt->aliases);
    ListCell *targetList;

    foreach (targetList, stmt->query->targetList) {
      TargetEntry *te = (TargetEntry *)lfirst(targetList);

      Assert(IsA(te, TargetEntry));
      /* junk columns don't get aliases */
      if (te->resjunk) continue;
      te->resname = pstrdup(strVal(lfirst(alist_item)));
      alist_item = lnext(alist_item);
      if (alist_item == NULL) break; /* done assigning aliases */
    }

    if (alist_item != NULL)
      ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
                      errmsg(
                          "CREATE VIEW specifies more column "
                          "names than columns")));
  }

  return result;
}

/*
 * transformDeleteStmt -
 *	  transforms a Delete Statement
 */
static Query *transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt) {
  Query *qry = makeNode(Query);
  Node *qual;

  qry->commandType = CMD_DELETE;

  /* setup database name for use of magma operations */
  MemoryContext oldContext = MemoryContextSwitchTo(MessageContext);

  int isGraph = parseAndTransformAsGraph(pstate, stmt->relation);

  char *dbname = stmt->relation->catalogname;
  database =
      (dbname != NULL) ? pstrdup(dbname) : get_database_name(MyDatabaseId);
  MemoryContextSwitchTo(oldContext);

  /* set up range table with just the result rel */
  qry->resultRelation = setTargetTable(
      pstate, stmt->relation, interpretInhOption(stmt->relation->inhOpt), true,
      ACL_DELETE);
if(isGraph)
  pstate->p_target_rangetblentry->graphName = stmt->relation->schemaname ?
      pstrdup(stmt->relation->schemaname) : NULL;
  qry->distinctClause = NIL;

  /*
   * The USING clause is non-standard SQL syntax, and is equivalent in
   * functionality to the FROM list that can be specified for UPDATE. The
   * USING keyword is used rather than FROM because FROM is already a
   * keyword in the DELETE syntax.
   */
  transformFromClause(pstate, stmt->usingClause);

  qual = transformWhereClause(pstate, stmt->whereClause, "WHERE");

/*
 * MPP-2506 [insert/update/delete] RETURNING clause not supported:
 *   We have problems processing the returning clause, so for now we have
 *   simply removed it and replaced it with an error message.
 */
#ifdef MPP_RETURNING_NOT_SUPPORTED
  if (stmt->returningList) {
    ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg(
                        "The RETURNING clause of the DELETE statement is not "
                        "supported in this version of Greenplum Database.")));
  }
#else
  qry->returningList = transformReturningList(pstate, stmt->returningList);
#endif

  /* CDB: Cursor position not available for errors below this point. */
  pstate->p_breadcrumb.node = NULL;

  /* done building the range table and jointree */
  qry->rtable = pstate->p_rtable;
  qry->jointree = makeFromExpr(pstate->p_joinlist, qual);

  qry->hasSubLinks = pstate->p_hasSubLinks;
  qry->hasAggs = pstate->p_hasAggs;
  if (pstate->p_hasAggs) parseCheckAggregates(pstate, qry);
  if (pstate->p_hasTblValueExpr) parseCheckTableFunctions(pstate, qry);

  return qry;
}
/*
 * transformInsertStmt -
 *	  transform an Insert Statement
 */
static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt,
                                  List **extras_before, List **extras_after) {
  Query *qry = makeNode(Query);
  SelectStmt *selectStmt = (SelectStmt *)stmt->selectStmt;
  List *exprList = NIL;
  bool isGeneralSelect;
  List *sub_rtable;
  List *sub_relnamespace;
  List *sub_varnamespace;
  List *icolumns;
  List *attrnos;
  RangeTblEntry *rte;
  RangeTblRef *rtr;
  ListCell *icols;
  ListCell *attnos;
  ListCell *lc;
  char *graphName;

  qry->commandType = CMD_INSERT;
  pstate->p_is_insert = true;

  /*
   * We have three cases to deal with: DEFAULT VALUES (selectStmt == NULL),
   * VALUES list, or general SELECT input.  We special-case VALUES, both for
   * efficiency and so we can handle DEFAULT specifications.
   */
  isGeneralSelect = (selectStmt && selectStmt->valuesLists == NIL);

  /*
   * If a non-nil rangetable/namespace was passed in, and we are doing
   * INSERT/SELECT, arrange to pass the rangetable/namespace down to the
   * SELECT.	This can only happen if we are inside a CREATE RULE, and in
   * that case we want the rule's OLD and NEW rtable entries to appear as
   * part of the SELECT's rtable, not as outer references for it.  (Kluge!)
   * The SELECT's joinlist is not affected however.  We must do this before
   * adding the target table to the INSERT's rtable.
   */
  if (isGeneralSelect) {
    sub_rtable = pstate->p_rtable;
    pstate->p_rtable = NIL;
    sub_relnamespace = pstate->p_relnamespace;
    pstate->p_relnamespace = NIL;
    sub_varnamespace = pstate->p_varnamespace;
    pstate->p_varnamespace = NIL;
  } else {
    sub_rtable = NIL; /* not used, but keep compiler quiet */
    sub_relnamespace = NIL;
    sub_varnamespace = NIL;
  }

  graphName = stmt->relation->schemaname ? pstrdup(stmt->relation->schemaname) : NULL;
  int isGraph = parseAndTransformAsGraph(pstate, stmt->relation);
  MemoryContext oldContext = MemoryContextSwitchTo(MessageContext);
  database =
      (stmt->relation->catalogname != NULL) ? pstrdup(stmt->relation->catalogname) : get_database_name(MyDatabaseId);
  MemoryContextSwitchTo(oldContext);
  /*
   * Must get write lock on INSERT target table before scanning SELECT, else
   * we will grab the wrong kind of initial lock if the target table is also
   * mentioned in the SELECT part.  Note that the target table is not added
   * to the joinlist or namespace.
   */
  qry->resultRelation =
      setTargetTable(pstate, stmt->relation, false, false, ACL_INSERT);
  if(isGraph)
    pstate->p_target_rangetblentry->graphName = graphName;
  /* Validate stmt->cols list, or build default list if no list given */
  icolumns = checkInsertTargets(pstate, stmt->cols, &attrnos);
  Assert(list_length(icolumns) == list_length(attrnos));

  /*
   * Determine which variant of INSERT we have.
   */
  if (selectStmt == NULL) {
    /*
     * We have INSERT ... DEFAULT VALUES.  We can handle this case by
     * emitting an empty targetlist --- all columns will be defaulted when
     * the planner expands the targetlist.
     */
    exprList = NIL;
  } else if (isGeneralSelect) {
    /*
     * We make the sub-pstate a child of the outer pstate so that it can
     * see any Param definitions supplied from above.  Since the outer
     * pstate's rtable and namespace are presently empty, there are no
     * side-effects of exposing names the sub-SELECT shouldn't be able to
     * see.
     */
    ParseState *sub_pstate = make_parsestate(pstate);
    Query *selectQuery;

    /*
     * Process the source SELECT.
     *
     * It is important that this be handled just like a standalone SELECT;
     * otherwise the behavior of SELECT within INSERT might be different
     * from a stand-alone SELECT. (Indeed, Postgres up through 6.5 had
     * bugs of just that nature...)
     */
    sub_pstate->p_rtable = sub_rtable;
    sub_pstate->p_relnamespace = sub_relnamespace;
    sub_pstate->p_varnamespace = sub_varnamespace;

    /*
     * Note: we are not expecting that extras_before and extras_after are
     * going to be used by the transformation of the SELECT statement.
     */
    selectQuery = transformStmt(sub_pstate, stmt->selectStmt, extras_before,
                                extras_after);

    release_pstate_resources(sub_pstate);
    free_parsestate(&sub_pstate);

    Assert(IsA(selectQuery, Query));
    Assert(selectQuery->commandType == CMD_SELECT);
    if (selectQuery->intoClause)
      ereport(ERROR,
              (errcode(ERRCODE_SYNTAX_ERROR),
               errmsg("INSERT ... SELECT cannot specify INTO"),
               parser_errposition(
                   pstate, exprLocation((Node *)selectQuery->intoClause))));

    /*
     * Make the source be a subquery in the INSERT's rangetable, and add
     * it to the INSERT's joinlist.
     */
    rte = addRangeTableEntryForSubquery(pstate, selectQuery,
                                        makeAlias("*SELECT*", NIL), false);
    rtr = makeNode(RangeTblRef);
    /* assume new rte is at end */
    rtr->rtindex = list_length(pstate->p_rtable);
    Assert(rte == rt_fetch(rtr->rtindex, pstate->p_rtable));
    pstate->p_joinlist = lappend(pstate->p_joinlist, rtr);

    /*----------
     * Generate an expression list for the INSERT that selects all the
     * non-resjunk columns from the subquery.  (INSERT's tlist must be
     * separate from the subquery's tlist because we may add columns,
     * insert datatype coercions, etc.)
     *
     * Const and Param nodes of type UNKNOWN in the SELECT's targetlist
     * no longer need special treatment here.  They'll be assigned proper
* types later by coerce_type() upon assignment to the target columns.
     * Otherwise this fails:  INSERT INTO foo SELECT 'bar', ... FROM baz
     *----------
     */
    expandRTE(rte, rtr->rtindex, 0, -1, false, NULL, &exprList);

    /* Prepare row for assignment to target table */
    exprList =
        transformInsertRow(pstate, exprList, stmt->cols, icolumns, attrnos);
  } else if (list_length(selectStmt->valuesLists) > 1) {
    /*
     * Process INSERT ... VALUES with multiple VALUES sublists. We
     * generate a VALUES RTE holding the transformed expression lists, and
     * build up a targetlist containing Vars that reference the VALUES
     * RTE.
     */
    List *exprsLists = NIL;
    int sublist_length = -1;

    foreach (lc, selectStmt->valuesLists) {
      List *sublist = (List *)lfirst(lc);

      /* CDB: In case of error, note which sublist is involved. */
      pstate->p_breadcrumb.node = (Node *)sublist;

      /* Do basic expression transformation (same as a ROW() expr) */
      sublist = transformExpressionList(pstate, sublist);

      /*
       * All the sublists must be the same length, *after*
       * transformation (which might expand '*' into multiple items).
       * The VALUES RTE can't handle anything different.
       */
      if (sublist_length < 0) {
        /* Remember post-transformation length of first sublist */
        sublist_length = list_length(sublist);
      } else if (sublist_length != list_length(sublist)) {
        ereport(ERROR,
                (errcode(ERRCODE_SYNTAX_ERROR),
                 errmsg("VALUES lists must all be the same length"),
                 parser_errposition(pstate, exprLocation((Node *)sublist))));
      }

      /* Prepare row for assignment to target table */
      sublist =
          transformInsertRow(pstate, sublist, stmt->cols, icolumns, attrnos);

      exprsLists = lappend(exprsLists, sublist);
    }

    /* CDB: Clear error location. */
    pstate->p_breadcrumb.node = NULL;

    /*
     * There mustn't have been any table references in the expressions,
     * else strange things would happen, like Cartesian products of those
     * tables with the VALUES list ...
     */
    if (pstate->p_joinlist != NIL)
      ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                      errmsg("VALUES must not contain table references")));

    /*
     * Another thing we can't currently support is NEW/OLD references in
     * rules --- seems we'd need something like SQL99's LATERAL construct
     * to ensure that the values would be available while evaluating the
     * VALUES RTE.	This is a shame.  FIXME
     */
    if (list_length(pstate->p_rtable) != 1 &&
        contain_vars_of_level((Node *)exprsLists, 0))
      ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                      errmsg("VALUES must not contain OLD or NEW references"),
                      errhint("Use SELECT ... UNION ALL ... instead.")));

    /*
     * Generate the VALUES RTE
     */
    rte = addRangeTableEntryForValues(pstate, exprsLists, NULL, true);
    rtr = makeNode(RangeTblRef);
    /* assume new rte is at end */
    rtr->rtindex = list_length(pstate->p_rtable);
    Assert(rte == rt_fetch(rtr->rtindex, pstate->p_rtable));
    pstate->p_joinlist = lappend(pstate->p_joinlist, rtr);

    /*
     * Generate list of Vars referencing the RTE
     */
    expandRTE(rte, rtr->rtindex, 0, -1, false, NULL, &exprList);
  } else {
    /*----------
     * Process INSERT ... VALUES with a single VALUES sublist.
     * We treat this separately for efficiency and for historical
     * compatibility --- specifically, allowing table references,
     * such as
     *			INSERT INTO foo VALUES(bar.*)
     *
     * The sublist is just computed directly as the Query's targetlist,
     * with no VALUES RTE.	So it works just like SELECT without FROM.
     *----------
     */
    List *valuesLists = selectStmt->valuesLists;

    Assert(list_length(valuesLists) == 1);

    /* Do basic expression transformation (same as a ROW() expr) */
    exprList = transformExpressionList(pstate, (List *)linitial(valuesLists));

    /* Prepare row for assignment to target table */
    exprList =
        transformInsertRow(pstate, exprList, stmt->cols, icolumns, attrnos);
  }

  /*
   * Generate query's target list using the computed list of expressions.
   */
  qry->targetList = NIL;
  icols = list_head(icolumns);
  attnos = list_head(attrnos);
  foreach (lc, exprList) {
    Expr *expr = (Expr *)lfirst(lc);
    ResTarget *col;
    TargetEntry *tle;

    col = (ResTarget *)lfirst(icols);
    Assert(IsA(col, ResTarget));

    tle =
        makeTargetEntry(expr, (AttrNumber)lfirst_int(attnos), col->name, false);
    qry->targetList = lappend(qry->targetList, tle);

    icols = lnext(icols);
    attnos = lnext(attnos);
  }

/*
 * MPP-2506 [insert/update/delete] RETURNING clause not supported:
 *   We have problems processing the returning clause, so for now we have
 *   simply removed it and replaced it with an error message.
 */
#ifdef MPP_RETURNING_NOT_SUPPORTED
  if (stmt->returningList) {
    ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg(
                        "The RETURNING clause of the INSERT statement is not "
                        "supported in this version of Greenplum Database.")));
  }
#else
  /*
   * If we have a RETURNING clause, we need to add the target relation to
   * the query namespace before processing it, so that Var references in
   * RETURNING will work.  Also, remove any namespace entries added in a
   * sub-SELECT or VALUES list.
   */
  if (stmt->returningList) {
    pstate->p_relnamespace = NIL;
    pstate->p_varnamespace = NIL;
    addRTEtoQuery(pstate, pstate->p_target_rangetblentry, false, true, true);
    qry->returningList = transformReturningList(pstate, stmt->returningList);
  }
#endif

  /* CDB: Cursor position not available for errors below this point. */
  pstate->p_breadcrumb.node = NULL;

  /* done building the range table and jointree */
  qry->rtable = pstate->p_rtable;
  qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);

  qry->hasSubLinks = pstate->p_hasSubLinks;
  /* aggregates not allowed (but subselects are okay) */
  if (pstate->p_hasAggs)
    ereport(ERROR, (errcode(ERRCODE_GROUPING_ERROR),
                    errmsg("cannot use aggregate function in VALUES")));
  if (pstate->p_hasWindFuncs)
    ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
                    errmsg("cannot use window function in VALUES")));

  return qry;
}

/*
 * Prepare an INSERT row for assignment to the target table.
 *
 * The row might be either a VALUES row, or variables referencing a
 * sub-SELECT output.
 */
static List *transformInsertRow(ParseState *pstate, List *exprlist,
                                List *stmtcols, List *icolumns, List *attrnos) {
  List *result;
  ListCell *lc;
  ListCell *icols;
  ListCell *attnos;

  /*
   * Check length of expr list.  It must not have more expressions than
   * there are target columns.  We allow fewer, but only if no explicit
   * columns list was given (the remaining columns are implicitly
   * defaulted).	Note we must check this *after* transformation because
   * that could expand '*' into multiple items.
   */
  if (list_length(exprlist) > list_length(icolumns))
    ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
                    errmsg("INSERT has more expressions than target columns"),
                    errOmitLocation(true)));
  if (stmtcols != NIL && list_length(exprlist) < list_length(icolumns))
    ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
                    errmsg("INSERT has more target columns than expressions"),
                    errOmitLocation(true)));

  /*
   * Prepare columns for assignment to target table.
   */
  result = NIL;
  icols = list_head(icolumns);
  attnos = list_head(attrnos);
  foreach (lc, exprlist) {
    Expr *expr = (Expr *)lfirst(lc);
    ResTarget *col;

    col = (ResTarget *)lfirst(icols);
    Assert(IsA(col, ResTarget));

    expr = transformAssignedExpr(pstate, expr, col->name, lfirst_int(attnos),
                                 col->indirection, col->location);

    result = lappend(result, expr);

    icols = lnext(icols);
    attnos = lnext(attnos);
  }

  return result;
}

/*
 * Tells the caller if CO is explicitly disabled, to handle cases where we
 * want to ignore encoding clauses in partition expansion.
 *
 * This is an ugly special case that backup expects to work and since we've got
 * tonnes of dumps out there and the possibility that users have learned this
 * grammar from them, we must continue to support it.
 */
static bool co_explicitly_disabled(List *opts) {
  ListCell *lc;

  foreach (lc, opts) {
    DefElem *el = lfirst(lc);
    char *arg = NULL;

    /* Arguement will be a Value */
    if (!el->arg) {
      continue;
    }

    bool need_free_arg = false;
    arg = defGetString(el, &need_free_arg);
    bool result = false;
    if (pg_strcasecmp("appendonly", el->defname) == 0 &&
        pg_strcasecmp("false", arg) == 0) {
      result = true;
    } else if (pg_strcasecmp("orientation", el->defname) == 0 &&
               pg_strcasecmp("column", arg) != 0) {
      result = true;
    }
    if (need_free_arg) {
      pfree(arg);
      arg = NULL;
    }

    AssertImply(need_free_arg, NULL == arg);

    if (result) {
      return true;
    }
  }
  return false;
}

/*
 * See if two encodings attempt to see the same parameters. If test_conflicts is
 * true, allow setting the same value, but the setting must be identical.
 */
static bool encodings_overlap(List *a, List *b, bool test_conflicts) {
  ListCell *lca;

  foreach (lca, a) {
    ListCell *lcb;
    DefElem *ela = lfirst(lca);

    foreach (lcb, b) {
      DefElem *elb = lfirst(lcb);

      if (pg_strcasecmp(ela->defname, elb->defname) == 0) {
        if (test_conflicts) {
          if (!ela->arg && !elb->arg)
            return true;
          else if (!ela->arg || !elb->arg) {
            /* skip */
          } else {
            bool need_free_ela = false;
            bool need_free_elb = false;
            char *ela_str = defGetString(ela, &need_free_ela);
            char *elb_str = defGetString(elb, &need_free_elb);
            int result = pg_strcasecmp(ela_str, elb_str);
            // free ela_str, elb_str if it is initialized via TypeNameToString
            if (need_free_ela) {
              pfree(ela_str);
              ela_str = NULL;
            }
            if (need_free_elb) {
              pfree(elb_str);
              elb_str = NULL;
            }

            AssertImply(need_free_ela, NULL == ela_str);
            AssertImply(need_free_elb, NULL == elb_str);

            if (result != 0) {
              return true;
            }
          }
        } else
          return true;
      }
    }
  }
  return false;
}

/*
 * Transform and validate the actual encoding clauses.
 *
 * We need tell the underlying system that these are AO/CO tables too,
 * hence the concatenation of the extra elements.
 */
List *transformStorageEncodingClause(List *options) {
  Datum d;
  List *extra =
      list_make1(makeDefElem("appendonly", (Node *)makeString("true")));
  //							 makeDefElem("orientation",
  //										 (Node
  //*)makeString("column")));

  /* add defaults for missing values */
  options = fillin_encoding(options);

  /*
   * The following two statements validate that the encoding clause is well
   * formed.
   */
  d = transformRelOptions(PointerGetDatum(NULL), list_concat(extra, options),
                          false, false);
  (void)heap_reloptions(0, d, true);

  return options;
}

/*
 * Validate the sanity of column reference storage clauses.
 *
 * 1. Ensure that we only refer to columns that exist.
 * 2. Ensure that each column is referenced either zero times or once.
 */
static void validateColumnStorageEncodingClauses(List *stenc,
                                                 CreateStmt *stmt) {
  ListCell *lc;
  struct HTAB *ht = NULL;
  struct colent {
    char colname[NAMEDATALEN];
    int count;
  } *ce = NULL;

  if (!stenc) return;

  /* Generate a hash table for all the columns */
  foreach (lc, stmt->base.tableElts) {
    Node *n = lfirst(lc);

    if (IsA(n, ColumnDef)) {
      ColumnDef *c = (ColumnDef *)n;
      char *colname;
      bool found = false;
      size_t n = NAMEDATALEN - 1 < strlen(c->colname) ? NAMEDATALEN - 1
                                                      : strlen(c->colname);

      colname = palloc0(NAMEDATALEN);
      MemSet(colname, 0, NAMEDATALEN);
      memcpy(colname, c->colname, n);
      colname[n] = '\0';

      if (!ht) {
        HASHCTL cacheInfo;
        int cacheFlags;

        memset(&cacheInfo, 0, sizeof(cacheInfo));
        cacheInfo.keysize = NAMEDATALEN;
        cacheInfo.entrysize = sizeof(*ce);
        cacheFlags = HASH_ELEM;

        ht = hash_create("column info cache", list_length(stmt->base.tableElts),
                         &cacheInfo, cacheFlags);
      }

      ce = hash_search(ht, colname, HASH_ENTER, &found);

      /*
       * The user specified a duplicate column name. We check duplicate
       * column names VERY late (under MergeAttributes(), which is called
       * by DefineRelation(). For the specific case here, it is safe to
       * call out that this is a duplicate. We don't need to delay until
       * we look at inheritance.
       */
      if (found) {
        ereport(ERROR, (errcode(ERRCODE_DUPLICATE_COLUMN),
                        errmsg("column \"%s\" duplicated", colname),
                        errOmitLocation(true)));
      }
      ce->count = 0;
    }
  }

  /*
   * If the table has no columns -- usually in the partitioning case -- then
   * we can short circuit.
   */
  if (!ht) return;

  /*
   * All column reference storage directives without the DEFAULT
   * clause should refer to real columns.
   */
  foreach (lc, stenc) {
    ColumnReferenceStorageDirective *c = lfirst(lc);

    Insist(IsA(c, ColumnReferenceStorageDirective));

    if (c->deflt)
      continue;
    else {
      bool found = false;
      char colname[NAMEDATALEN];
      size_t collen = strlen(strVal(c->column));
      size_t n = NAMEDATALEN - 1 < collen ? NAMEDATALEN - 1 : collen;
      MemSet(colname, 0, NAMEDATALEN);
      memcpy(colname, strVal(c->column), n);
      colname[n] = '\0';

      ce = hash_search(ht, colname, HASH_FIND, &found);

      if (!found) elog(ERROR, "column \"%s\" does not exist", colname);

      ce->count++;

      if (ce->count > 1)
        elog(ERROR,
             "column \"%s\" referenced in more than one "
             "COLUMN ENCODING clause",
             colname);
    }
  }

  hash_destroy(ht);
}

/*
 * Find the column reference storage encoding clause for `column'.
 *
 * This is called by transformAttributeEncoding() in a loop but stenc should be
 * quite small in practice.
 */
static ColumnReferenceStorageDirective *find_crsd(Value *column, List *stenc) {
  ListCell *lc;

  foreach (lc, stenc) {
    ColumnReferenceStorageDirective *c = lfirst(lc);

    if (c->deflt == false && equal(column, c->column)) return c;
  }
  return NULL;
}

List *TypeNameGetStorageDirective(TypeName *typname) {
  HeapTuple tuple;
  cqContext *pcqCtx;
  Oid typid = typenameTypeId(NULL, typname);
  List *out = NIL;

  /* XXX XXX: SELECT typoptions */
  pcqCtx = caql_beginscan(NULL, cql("SELECT * FROM pg_type_encoding "
                                    " WHERE typid = :1 ",
                                    ObjectIdGetDatum(typid)));

  tuple = caql_getnext(pcqCtx);

  if (HeapTupleIsValid(tuple)) {
    Datum options;
    bool isnull;

    options = caql_getattr(pcqCtx, Anum_pg_type_encoding_typoptions, &isnull);

    Insist(!isnull);

    out = untransformRelOptions(options);
  }

  caql_endscan(pcqCtx);
  return out;
}

/*
 * Make a default column storage directive from a WITH clause
 * Ignore options in the WITH clause that don't appear in
 * storage_directives for column-level compression.
 */
List *form_default_storage_directive(List *enc) {
  List *out = NIL;
  ListCell *lc;
  bool parquetTable = false;
  bool pagesizeSet = false;
  bool rowgroupsizeSet = false;
  bool need_free_arg = false;
  foreach (lc, enc) {
    DefElem *el = lfirst(lc);

    if (!el->defname) out = lappend(out, copyObject(el));

    if (pg_strcasecmp("appendonly", el->defname) == 0) continue;
    if (pg_strcasecmp("oids", el->defname) == 0) continue;
    if (pg_strcasecmp("errortable", el->defname) == 0) continue;
    if (pg_strcasecmp("fillfactor", el->defname) == 0) continue;
    if (pg_strcasecmp("tablename", el->defname) == 0) continue;
    if (pg_strcasecmp("orientation", el->defname) == 0) {
      if (el->arg == NULL)
        insist_log(
            false,
            "syntax not correct, orientation should has corresponding value");
      if (pg_strcasecmp("column", defGetString(el, &need_free_arg)) == 0) {
        continue;
      }
      if (pg_strcasecmp("parquet", defGetString(el, &need_free_arg)) == 0)
        parquetTable = true;
    }
    if (pg_strcasecmp("pagesize", el->defname) == 0) pagesizeSet = true;
    if (pg_strcasecmp("rowgroupsize", el->defname) == 0) rowgroupsizeSet = true;

    out = lappend(out, copyObject(el));
  }

  /* If parquet table, but pagesize/rowgroupsize not set, should set them to
   * default value*/
  if (parquetTable) {
    if (!pagesizeSet) {
      DefElem *f = makeNode(DefElem);
      f->defname = "pagesize";
      f->arg = (Node *)makeInteger(DEFAULT_PARQUET_PAGE_SIZE_PARTITION);
      out = lappend(out, f);
    }
    if (!rowgroupsizeSet) {
      DefElem *f = makeNode(DefElem);
      f->defname = "rowgroupsize";
      f->arg = (Node *)makeInteger(DEFAULT_PARQUET_ROWGROUP_SIZE_PARTITION);
      out = lappend(out, f);
    }
  }

  return out;
}

static List *transformAttributeEncoding(List *stenc, CreateStmt *stmt,
                                        CreateStmtContext cxt) {
  ListCell *lc;
  bool found_enc = stenc != NIL;
  ColumnReferenceStorageDirective *deflt = NULL;
  List *newenc = NIL;
  List *tmpenc;

#define UNSUPPORTED_ORIENTATION_ERROR()        \
  ereport(                                     \
      ERROR,                                   \
      (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \
       errmsg("ENCODING clause only supported with column oriented tables")))

  /*
   * The migrator puts lots of confusing things in the WITH() clause. We never
   * expect to do AOCO table creation during upgrade so bail out.
   */
  if (gp_upgrade_mode) {
    return NIL;
  }

  /* We only support the attribute encoding clause on AOCS tables */
  if (stenc) UNSUPPORTED_ORIENTATION_ERROR();

  /* get the default clause, if there is one. */
  foreach (lc, stenc) {
    ColumnReferenceStorageDirective *c = lfirst(lc);
    Insist(IsA(c, ColumnReferenceStorageDirective));

    if (c->deflt) {
      /*
       * Some quick validation: there should only be one default
       * clause
       */
      if (deflt)
        elog(ERROR, "only one default column encoding may be specified");
      else {
        deflt = c;
        deflt->encoding = transformStorageEncodingClause(deflt->encoding);

        /*
         * The default encoding and the with clause better not
         * try and set the same options!
         */

        if (encodings_overlap(stmt->base.options, c->encoding, false))
          ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                          errmsg(
                              "DEFAULT COLUMN ENCODING clause cannot "
                              "override values set in WITH clause")));
      }
    }
  }

  /*
   * If no default has been specified, we might create one out of the
   * WITH clause.
   */
  if (!deflt) {
    tmpenc = form_default_storage_directive(stmt->base.options);
  } else {
    tmpenc = NIL;
  }

  if (tmpenc) {
    deflt = makeNode(ColumnReferenceStorageDirective);
    deflt->deflt = true;
    deflt->encoding = transformStorageEncodingClause(tmpenc);
  }

  /*
   * Loop over all columns. If a column has a column reference storage clause
   * -- i.e., COLUMN name ENCODING () -- apply that. Otherwise, apply the
   * default.
   */
  foreach (lc, cxt.columns) {
    Node *n = lfirst(lc);
    ColumnDef *d = (ColumnDef *)n;
    ColumnReferenceStorageDirective *c =
        makeNode(ColumnReferenceStorageDirective);

    Insist(IsA(d, ColumnDef));

    c->column = makeString(pstrdup(d->colname));

    if (d->encoding) {
      found_enc = true;
      c->encoding = d->encoding;
      c->encoding = transformStorageEncodingClause(c->encoding);
    } else {
      /*
       * No explicit encoding clause but we may still have a
       * clause if
       * i. There's a column reference storage directive for this
       * column
       * ii. There's a default column encoding
       * iii. There's a default for the type.
       *
       * If none of these is the case, we set an 'empty' encoding
       * clause.
       */

      /*
       * We use stenc here -- the storage encoding directives
       * gleaned from the table elements list because we know
       * there's nothing to look at in new_enc, since we're
       * generating that
       */
      ColumnReferenceStorageDirective *s = find_crsd(c->column, stenc);

      if (s) {
        s->encoding = transformStorageEncodingClause(s->encoding);
        newenc = lappend(newenc, s);
        continue;
      }

      /* ... and so we beat on, boats against the current... */
      if (deflt) {
        c->encoding = copyObject(deflt->encoding);
      } else {
        List *te = TypeNameGetStorageDirective(d->typname);

        if (te) {
          c->encoding = copyObject(te);
        } else
          c->encoding = default_column_encoding_clause();
      }
    }
    newenc = lappend(newenc, c);
  }

  /* Check again incase we expanded a some column encoding clauses */
  if (found_enc)
    UNSUPPORTED_ORIENTATION_ERROR();
  else
    return NULL;

  validateColumnStorageEncodingClauses(newenc, stmt);

  return newenc;
}

Query *transformCreateStmtImpl(ParseState *pstate,
                               CreateStmt *stmt,
                                          List **extras_before,
                                          List **extras_after) {
  return transformCreateStmt(pstate, stmt, extras_before, extras_after);
}

/*
 * transformCreateStmt -
 *	  transforms the "create table" statement
 *	  SQL92 allows constraints to be scattered all over, so thumb through
 *	   the columns and collect all constraints into one place.
 *	  If there are any implied indices (e.g. UNIQUE or PRIMARY KEY)
 *	   then expand those into multiple IndexStmt blocks.
 *	  - thomas 1997-12-02
 */
Query *transformCreateStmt(ParseState *pstate, CreateStmt *stmt,
                           List **extras_before, List **extras_after) {
  CreateStmtContext cxt;
  Query *q;
  ListCell *elements;
  List *likeDistributedBy = NIL;
  bool bQuiet = false; /* shut up transformDistributedBy messages */
  List *stenc = NIL;   /* column reference storage encoding clauses */

  cxt.stmtType = "CREATE TABLE";
  cxt.isExternalTable = false;
  cxt.relation = stmt->base.relation;
  cxt.inhRelations = stmt->base.inhRelations;
  cxt.isalter = false;
  cxt.isaddpart = stmt->base.is_add_part;
  cxt.columns = NIL;
  cxt.ckconstraints = NIL;
  cxt.fkconstraints = NIL;
  cxt.ixconstraints = NIL;
  cxt.inh_indexes = NIL;
  cxt.blist = NIL;
  cxt.alist = NIL;
  cxt.dlist = NIL; /* for deferred analysis requiring the created table */
  cxt.pkey = NULL;
  cxt.exttypedesc = NULL;
  cxt.hasoids = interpretOidsOption(stmt->base.options);

  stmt->policy = NULL;

  /* Disallow inheritance in combination with partitioning. */
  if (stmt->base.inhRelations &&
      (stmt->base.partitionBy || stmt->base.is_part_child)) {
    ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                    errmsg("cannot mix inheritance with partitioning")));
  }

  /* Only on top-most partitioned tables. */
  if (stmt->base.partitionBy && !stmt->base.is_part_child) {
    fixCreateStmtForPartitionedTable(&stmt->base);
  }

  /*
   * Run through each primary element in the table creation clause. Separate
   * column defs from constraints, and do preliminary analysis.
   */
  foreach (elements, stmt->base.tableElts) {
    Node *element = lfirst(elements);

    switch (nodeTag(element)) {
      case T_ColumnDef:
        transformColumnDefinition(pstate, &cxt, (ColumnDef *)element);
        break;

      case T_Constraint:
        transformTableConstraint(pstate, &cxt, (Constraint *)element);
        break;

      case T_FkConstraint:
        /* No pre-transformation needed */
        cxt.fkconstraints = lappend(cxt.fkconstraints, element);
        break;

      case T_InhRelation: {
        bool isBeginning = (cxt.columns == NIL);

        transformInhRelation(pstate, &cxt, (InhRelation *)element, false);

        if (Gp_role == GP_ROLE_DISPATCH && isBeginning &&
            stmt->base.distributedBy == NIL && stmt->base.inhRelations == NIL &&
            stmt->policy == NULL) {
          likeDistributedBy = getLikeDistributionPolicy((InhRelation *)element);
        }
      } break;

      case T_ColumnReferenceStorageDirective:
        /* processed below in transformAttributeEncoding() */
        stenc = lappend(stenc, element);
        break;

      default:
        elog(ERROR, "unrecognized node type: %d", (int)nodeTag(element));
        break;
    }
  }

  /*
   * transformIndexConstraints wants cxt.alist to contain only index
   * statements, so transfer anything we already have into extras_after
   * immediately.
   */
  *extras_after = list_concat(cxt.alist, *extras_after);
  cxt.alist = NIL;

  Assert(stmt->base.constraints == NIL);

  /*
   * Postprocess constraints that give rise to index definitions.
   */
  transformIndexConstraints(pstate, &cxt,
                            stmt->base.is_add_part || stmt->is_split_part);

  /*
   * Carry any deferred analysis statements forward.  Added for MPP-13750
   * but should also apply to the similar case involving simple inheritance.
   */
  if (cxt.dlist) {
    stmt->deferredStmts = list_concat(stmt->deferredStmts, cxt.dlist);
    cxt.dlist = NIL;
  }

  /*
   * Postprocess foreign-key constraints.
   * But don't cascade FK constraints to parts, yet.
   */
  if (!stmt->base.is_part_child)
    transformFKConstraints(pstate, &cxt, true, false);

  /*
   * Analyze attribute encoding clauses.
   *
   * Partitioning configurations may have things like:
   *
   * CREATE TABLE ...
   *  ( a int ENCODING (...))
   * WITH (appendonly=true, orientation=column)
   * PARTITION BY ...
   * (PARTITION ... WITH (appendonly=false));
   *
   * We don't want to throw an error when we try to apply the ENCODING clause
   * to the partition which the user wants to be non-AO. Just ignore it
   * instead.
   */
  if (stmt->base.is_part_child) {
    if (co_explicitly_disabled(stmt->base.options) || !stenc)
      stmt->attr_encodings = NIL;
    else {
      ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                      errmsg(
                          "ENCODING clause only supported with "
                          "column oriented partitioned tables")));
    }
  } else
    stmt->attr_encodings = transformAttributeEncoding(stenc, stmt, cxt);

  /*
   * Postprocess Greenplum Database distribution columns
   */
  if (stmt->base.is_part_child ||
      (stmt->base.partitionBy &&
       (
           /* be very quiet if set subpartn template */
           (((PartitionBy *)(stmt->base.partitionBy))->partQuiet ==
            PART_VERBO_NOPARTNAME) ||
           (
               /* quiet for partitions of depth > 0 */
               (((PartitionBy *)(stmt->base.partitionBy))->partDepth != 0) &&
               (((PartitionBy *)(stmt->base.partitionBy))->partQuiet !=
                PART_VERBO_NORMAL)))))
    bQuiet = true; /* silence distro messages for partitions */

  transformDistributedBy(pstate, &cxt, stmt->base.distributedBy, &stmt->policy,
                         stmt->base.options, likeDistributedBy, bQuiet);

  /*
   * Process table partitioning clause
   */
  transformPartitionBy(pstate, &cxt, &stmt->base, stmt->base.partitionBy,
                       stmt->policy);

  /*
   * Output results.
   */
  q = makeNode(Query);
  q->commandType = CMD_UTILITY;
  q->utilityStmt = (Node *)stmt;
  stmt->base.tableElts = cxt.columns;
  stmt->base.constraints = cxt.ckconstraints;
  *extras_before = list_concat(*extras_before, cxt.blist);
  *extras_after = list_concat(cxt.alist, *extras_after);

  return q;
}

enum PreDefinedFormatterOptionVALTYPE {
  PREDEF_FMTOPT_VAL_NO,
  PREDEF_FMTOPT_VAL_STRING,
  PREDEF_FMTOPT_VAL_SIGNEDINTEGER,
  PREDEF_FMTOPT_VAL_COLNAMELIST
};

enum PreDefinedFormatterOptionID {
  PREDEF_FMT_OPT_ID_DELIMITER,
  PREDEF_FMT_OPT_ID_NULL,
  PREDEF_FMT_OPT_ID_HEADER,
  PREDEF_FMT_OPT_ID_QUOTE,
  PREDEF_FMT_OPT_ID_ESCAPE,
  PREDEF_FMT_OPT_ID_FORCENOTNULL,
  PREDEF_FMT_OPT_ID_FORCEQUOTE,
  PREDEF_FMT_OPT_ID_FILLMISSINGFIELDS,
  PREDEF_FMT_OPT_ID_NEWLINE,
  PREDEF_FMT_OPT_ID_UNPREDEFINED,
  PREDEF_FMT_OPT_ID_ILLEGAL
};

typedef struct PreDefinedFormatterOption {
  char keyword[3][32];
  int nKeyword;
  bool hasValue;
  enum PreDefinedFormatterOptionVALTYPE valueType;
  enum PreDefinedFormatterOptionID optID;
} PreDefinedFormatterOption;

#define PREDEF_FMTOPT_SIZE 9

enum PreDefinedFormatterOptionID MatchExternalRelationFormatterOption(
    PreDefinedFormatterOption *options, ListCell *head) {
  ListCell *p1 = head;
  ListCell *p2 = head->next;
  ListCell *p3 = p2 == NULL ? NULL : p2->next;
  ListCell *p4 = p3 == NULL ? NULL : p3->next;

  DefElem *de1 = (DefElem *)lfirst(p1);
  DefElem *de2 = p2 == NULL ? NULL : (DefElem *)lfirst(p2);
  DefElem *de3 = p3 == NULL ? NULL : (DefElem *)lfirst(p3);
  DefElem *de4 = p4 == NULL ? NULL : (DefElem *)lfirst(p4);

  if (strcmp("#ident", de1->defname) != 0) {
    return PREDEF_FMT_OPT_ID_ILLEGAL; /* must start with a #ident elem */
  }

  for (int i = 0; i < PREDEF_FMTOPT_SIZE; ++i) {
    PreDefinedFormatterOption *pdOpt = &(options[i]);
    if (pdOpt->nKeyword == 1 &&
        strcasecmp(pdOpt->keyword[0], ((Value *)(de1->arg))->val.str) == 0) {
      if (!options[i].hasValue) {
        return options[i].optID; /* Got no value option */
      } else if (p2 == NULL) {
        return PREDEF_FMT_OPT_ID_ILLEGAL; /* no expected value field */
      } else if (((strcmp("#string", de2->defname) == 0) &&
                  (options[i].valueType == PREDEF_FMTOPT_VAL_STRING)) ||
                 ((strcmp("#int", de2->defname) == 0) &&
                  (options[i].valueType == PREDEF_FMTOPT_VAL_SIGNEDINTEGER)) ||
                 ((strcmp("#collist", de2->defname) == 0) &&
                  (options[i].valueType == PREDEF_FMTOPT_VAL_COLNAMELIST)) ||
                 ((strcmp("#ident", de2->defname) == 0) &&
                  (options[i].valueType == PREDEF_FMTOPT_VAL_COLNAMELIST))) {
        return options[i].optID; /* Got option having one value */
      } else {
        return PREDEF_FMT_OPT_ID_ILLEGAL; /* no expected value type */
      }
    } else if (pdOpt->nKeyword == 2 && de2 != NULL &&
               strcasecmp(pdOpt->keyword[0], ((Value *)(de1->arg))->val.str) ==
                   0 &&
               strcasecmp(pdOpt->keyword[1], ((Value *)(de2->arg))->val.str) ==
                   0) {
      if (!options[i].hasValue) {
        return options[i].optID; /* got no value option */
      } else if (de3 == NULL) {
        return PREDEF_FMT_OPT_ID_ILLEGAL; /* no expected value field */
      } else if (((strcmp("#string", de3->defname) == 0) &&
                  (options[i].valueType == PREDEF_FMTOPT_VAL_STRING)) ||
                 ((strcmp("#int", de3->defname) == 0) &&
                  (options[i].valueType == PREDEF_FMTOPT_VAL_SIGNEDINTEGER)) ||
                 ((strcmp("#collist", de3->defname) == 0) &&
                  (options[i].valueType == PREDEF_FMTOPT_VAL_COLNAMELIST)) ||
                 ((strcmp("#ident", de3->defname) == 0) &&
                  (options[i].valueType == PREDEF_FMTOPT_VAL_COLNAMELIST))) {
        return options[i].optID; /* Got option having one value */
      } else {
        return PREDEF_FMT_OPT_ID_ILLEGAL; /* no expected value type */
      }
    } else if (pdOpt->nKeyword == 3 && de2 != NULL && de3 != NULL &&
               strcasecmp(pdOpt->keyword[0], ((Value *)(de1->arg))->val.str) ==
                   0 &&
               strcasecmp(pdOpt->keyword[1], ((Value *)(de2->arg))->val.str) ==
                   0 &&
               strcasecmp(pdOpt->keyword[2], ((Value *)(de3->arg))->val.str) ==
                   0) {
      if (!options[i].hasValue) {
        return options[i].optID; /* got no value option */
      } else if (de4 == NULL) {
        return PREDEF_FMT_OPT_ID_ILLEGAL; /* no expected value field */
      } else if (((strcmp("#string", de4->defname) == 0) &&
                  (options[i].valueType == PREDEF_FMTOPT_VAL_STRING)) ||
                 ((strcmp("#int", de4->defname) == 0) &&
                  (options[i].valueType == PREDEF_FMTOPT_VAL_SIGNEDINTEGER)) ||
                 ((strcmp("#collist", de4->defname) == 0) &&
                  (options[i].valueType == PREDEF_FMTOPT_VAL_COLNAMELIST)) ||
                 ((strcmp("#ident", de4->defname) == 0) &&
                  (options[i].valueType == PREDEF_FMTOPT_VAL_COLNAMELIST))) {
        return options[i].optID; /* Got option having one value */
      } else {
        return PREDEF_FMT_OPT_ID_ILLEGAL; /* no expected value type */
      }
    }
  }

  /*
   * We expect user defined special options which should be consumed
   * further by customized formatter.
   */
  return PREDEF_FMT_OPT_ID_UNPREDEFINED;
}

void recognizeExternalRelationFormatterOptions(
    CreateExternalStmt *createExtStmt) {
  PreDefinedFormatterOption options[PREDEF_FMTOPT_SIZE] = {
      {{"delimiter", "", ""},
       1,
       true,
       PREDEF_FMTOPT_VAL_STRING,
       PREDEF_FMT_OPT_ID_DELIMITER},
      {{"null", "", ""},
       1,
       true,
       PREDEF_FMTOPT_VAL_STRING,
       PREDEF_FMT_OPT_ID_NULL},
      {{"header", "", ""},
       1,
       false,
       PREDEF_FMTOPT_VAL_NO,
       PREDEF_FMT_OPT_ID_HEADER},
      {{"quote", "", ""},
       1,
       true,
       PREDEF_FMTOPT_VAL_STRING,
       PREDEF_FMT_OPT_ID_QUOTE},
      {{"escape", "", ""},
       1,
       true,
       PREDEF_FMTOPT_VAL_STRING,
       PREDEF_FMT_OPT_ID_ESCAPE},
      {{"force", "not", "null"},
       3,
       true,
       PREDEF_FMTOPT_VAL_COLNAMELIST,
       PREDEF_FMT_OPT_ID_FORCENOTNULL},
      {{"force", "quote", ""},
       2,
       true,
       PREDEF_FMTOPT_VAL_COLNAMELIST,
       PREDEF_FMT_OPT_ID_FORCEQUOTE},
      {{"fill", "missing", "fields"},
       3,
       false,
       PREDEF_FMTOPT_VAL_NO,
       PREDEF_FMT_OPT_ID_FILLMISSINGFIELDS},
      {{"newline", "", ""},
       1,
       true,
       PREDEF_FMTOPT_VAL_STRING,
       PREDEF_FMT_OPT_ID_NEWLINE}};

  List *newOpts = NULL;
  ListCell *optCell = list_head(createExtStmt->base.options);

  /* Add restriction of error lines */
  if (createExtStmt->sreh != NULL) {
    /* Handle error table specification and reject number per segment */
    SingleRowErrorDesc *errDesc = (SingleRowErrorDesc *)createExtStmt->sreh;
    if (errDesc->rejectlimit > 0 && errDesc->is_hdfs_protocol_text) {
      newOpts = lappend(
          newOpts,
          makeDefElem("reject_limit", makeInteger(errDesc->rejectlimit)));
      if (errDesc->hdfsLoc)
        newOpts =
            lappend(newOpts,
                    makeDefElem("err_table",
                                (Node *)makeString(pstrdup(errDesc->hdfsLoc))));
    }
  }

  while (optCell != NULL) {
    /* Try a match now. */
    enum PreDefinedFormatterOptionID id =
        MatchExternalRelationFormatterOption(options, optCell);
    switch (id) {
      case PREDEF_FMT_OPT_ID_DELIMITER: {
        DefElem *de = (DefElem *)lfirst(optCell->next);
        Value *v = (Value *)(de->arg);
        DefElem *newde =
            makeDefElem("delimiter", (Node *)makeString(v->val.str));
        newOpts = lappend(newOpts, newde);
        optCell = optCell->next->next;
        break;
      }
      case PREDEF_FMT_OPT_ID_NULL: {
        DefElem *de = (DefElem *)lfirst(optCell->next);
        Value *v = (Value *)(de->arg);
        DefElem *newde = makeDefElem("null", (Node *)makeString(v->val.str));
        newOpts = lappend(newOpts, newde);
        optCell = optCell->next->next;
        break;
      }
      case PREDEF_FMT_OPT_ID_HEADER: {
        DefElem *newde = makeDefElem("header", (Node *)makeInteger(TRUE));
        newOpts = lappend(newOpts, newde);
        optCell = optCell->next;
        break;
      }
      case PREDEF_FMT_OPT_ID_QUOTE: {
        DefElem *de = (DefElem *)lfirst(optCell->next);
        Value *v = (Value *)(de->arg);
        DefElem *newde = makeDefElem("quote", (Node *)makeString(v->val.str));
        newOpts = lappend(newOpts, newde);
        optCell = optCell->next->next;
        break;
      }
      case PREDEF_FMT_OPT_ID_ESCAPE: {
        DefElem *de = (DefElem *)lfirst(optCell->next);
        Value *v = (Value *)(de->arg);
        DefElem *newde = makeDefElem("escape", (Node *)makeString(v->val.str));
        newOpts = lappend(newOpts, newde);
        optCell = optCell->next->next;
        break;
      }
      case PREDEF_FMT_OPT_ID_FORCENOTNULL: {
        DefElem *newde = NULL;
        DefElem *de = (DefElem *)lfirst(optCell->next->next->next);
        if (strcmp("#ident", de->defname) == 0) {
          /*
           * The case there is only one column name which is recognized
           * as a ident string.
           */
          Value *v = (Value *)(de->arg);
          List *collist = list_make1(makeString(v->val.str));
          newde = makeDefElem("force_notnull", (Node *)collist);

        } else {
          /*
           * There are multiple column names in a list already
           * recognized by parser.
           */
          List *collist = NULL;
          ListCell *colCell = NULL;
          foreach (colCell, (List *)(de->arg)) {
            collist = lappend(collist,
                              makeString(((Value *)lfirst(colCell))->val.str));
            elog(LOG, "recognized column list colname:%s",
                 ((Value *)lfirst(colCell))->val.str);
          }
          newde = makeDefElem("force_notnull", (Node *)collist);

          /* TODO: check where the old instance is freed */
        }
        newOpts = lappend(newOpts, newde);
        optCell = optCell->next->next->next->next;
        break;
      }
      case PREDEF_FMT_OPT_ID_FORCEQUOTE: {
        DefElem *newde = NULL;
        DefElem *de = (DefElem *)lfirst(optCell->next->next);
        if (strcmp("#ident", de->defname) == 0) {
          /*
           * The case there is only one column name which is recognized
           * as a ident string.
           */
          Value *v = (Value *)(de->arg);
          List *collist = list_make1(makeString(v->val.str));
          newde = makeDefElem("force_quote", (Node *)collist);

        } else {
          /*
           * There are multiple column names in a list already
           * recognized by parser.
           */
          List *collist = NULL;
          ListCell *colCell = NULL;
          foreach (colCell, (List *)(de->arg)) {
            collist = lappend(collist,
                              makeString(((Value *)lfirst(colCell))->val.str));
            elog(LOG, "recognized column list colname:%s",
                 ((Value *)lfirst(colCell))->val.str);
          }
          newde = makeDefElem("force_quote", (Node *)collist);

          /* TODO: check where the old instance is freed */
        }
        newOpts = lappend(newOpts, newde);
        optCell = optCell->next->next->next;
        break;
      }
      case PREDEF_FMT_OPT_ID_FILLMISSINGFIELDS: {
        DefElem *newde =
            makeDefElem("fill_missing_fields", (Node *)makeInteger(TRUE));
        newOpts = lappend(newOpts, newde);
        optCell = optCell->next->next->next;
        break;
      }
      case PREDEF_FMT_OPT_ID_NEWLINE: {
        DefElem *de = (DefElem *)lfirst(optCell->next);
        Value *v = (Value *)(de->arg);
        DefElem *newde = makeDefElem("newline", (Node *)makeString(v->val.str));
        newOpts = lappend(newOpts, newde);
        optCell = optCell->next->next;
        break;
      }
      case PREDEF_FMT_OPT_ID_UNPREDEFINED: {
        /*
         * In case it is a user defined option. we combind all continuous
         * ident until we see a string constant or a integer constant.
         * So this means user defined formatter's user defined option
         * values can only be string or integer values.
         */
        int c = 0;
        int identlength = 0;
        ListCell *walkerCell = optCell;
        while (walkerCell != NULL &&
               strcmp("#ident", ((DefElem *)lfirst(walkerCell))->defname) ==
                   0) {
          c++;
          Value *v = (Value *)(((DefElem *)lfirst(walkerCell))->arg);
          identlength += strlen(v->val.str) + 1;
          walkerCell = walkerCell->next;
        }

        /* Decide the value part */
        Node *value = NULL;
        if (walkerCell == NULL) {
          /* The case the option without value. we set TRUE for it. */
          value = makeInteger(TRUE);
        } else {
          DefElem *de = (DefElem *)lfirst(walkerCell);
          if (strcmp("#collist", de->defname) == 0) {
            /*
             * We don't accept column name list value types for
             * customized formatter's user defined options.
             */
            ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                            errmsg(
                                "cannot support column name list as an unknown "
                                "option's value"),
                            errOmitLocation(true)));
          } else if (strcmp("#int", de->defname) == 0) {
            value = makeInteger(((Value *)(de->arg))->val.ival);
          } else {
            value = makeString(((Value *)(de->arg))->val.str);
          }
        }

        /* Build key part. */
        char *newKey = (char *)palloc0(sizeof(char) * identlength);
        ListCell *walkerCell2 = optCell;
        int counter = 0;
        for (; counter < c; counter++, walkerCell2 = walkerCell2->next) {
          Value *v = (Value *)(((DefElem *)lfirst(walkerCell2))->arg);
          if (counter > 0) {
            strcat(newKey, "_");
          }
          strcat(newKey, v->val.str);
        }

        DefElem *newde = makeDefElem(newKey, (Node *)value);
        newOpts = lappend(newOpts, newde);

        if (walkerCell)
          optCell = walkerCell->next;
        else
          optCell = NULL;
        break;
      }
      case PREDEF_FMT_OPT_ID_ILLEGAL: {
        ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
                        errmsg("cannot recognize full formatter option list"),
                        errOmitLocation(true)));
      }
    }
  }

  /* Use new list to replace the old one */
  createExtStmt->base.options = newOpts;
}

static Query *transformCreateVlabelStmt(ParseState *pstate,
                                          CreateVlabelStmt *stmt,
                                          List **extras_before,
                                          List **extras_after) {
  CreateStmtContext cxt;
  Query *q;

  cxt.stmtType = "CREATE VERTEX";
  cxt.relation = stmt->relation;
  cxt.hasoids = false;
  cxt.isalter = false;
  cxt.columns = NIL;
  cxt.ckconstraints = NIL;
  cxt.fkconstraints = NIL;
  cxt.ixconstraints = NIL;
  cxt.inh_indexes = NIL;
  cxt.pkey = NULL;

  cxt.blist = NIL;
  cxt.alist = NIL;

  ListCell *elements;
  foreach (elements, stmt->tableElts) {
    Node *element = lfirst(elements);

    switch (nodeTag(element)) {
      case T_ColumnDef:
        transformColumnDefinition(pstate, &cxt, (ColumnDef *)element);
        ColumnDef *column = (ColumnDef *)element;
        Type type = typenameType(NULL, column->typname);
        column->typname->typid = typeTypeId(type);
        ReleaseType(type);
        break;

      case T_Constraint:
        transformExtTableConstraint(pstate, &cxt, (Constraint *)element);
        break;

      case T_FkConstraint:
        /* should never happen. If it does fix gram.y */
        elog(ERROR, "node type %d not supported for vlabel",
             (int)nodeTag(element));
        break;

      default:
        elog(ERROR, "unrecognized node type: %d", (int)nodeTag(element));
        break;
    }
  }

  /*
   * transformIndexConstraints wants cxt.alist to contain only index
   * statements, so transfer anything we already have into extras_after
   * immediately.
   */
  *extras_after = list_concat(cxt.alist, *extras_after);
  cxt.alist = NIL;

  /*
   * Output results.
   */
  q = makeNode(Query);
  q->commandType = CMD_UTILITY;
  q->utilityStmt = (Node *)stmt;
  stmt->tableElts = cxt.columns;
  stmt->constraints = cxt.ixconstraints;
  *extras_before = list_concat(*extras_before, cxt.blist);
  *extras_after = list_concat(cxt.alist, *extras_after);
  return q;
}

static Query *transformCreateElabelStmt(ParseState *pstate,
                                          CreateElabelStmt *stmt,
                                          List **extras_before,
                                          List **extras_after) {
  CreateStmtContext cxt;
  Query *q;

  cxt.stmtType = "CREATE EDGE";
  cxt.relation = stmt->relation;
  cxt.hasoids = false;
  cxt.isalter = false;
  cxt.columns = NIL;
  cxt.ckconstraints = NIL;
  cxt.fkconstraints = NIL;
  cxt.ixconstraints = NIL;
  cxt.inh_indexes = NIL;
  cxt.pkey = NULL;

  cxt.blist = NIL;
  cxt.alist = NIL;

  ListCell *elements;
  foreach (elements, stmt->tableElts) {
    Node *element = lfirst(elements);

    switch (nodeTag(element)) {
      case T_ColumnDef:
        transformColumnDefinition(pstate, &cxt, (ColumnDef *)element);
        ColumnDef *column = (ColumnDef *)element;
        Type type = typenameType(NULL, column->typname);
        column->typname->typid = typeTypeId(type);
        ReleaseType(type);
        break;

      case T_Constraint:
        transformExtTableConstraint(pstate, &cxt, (Constraint *)element);
        break;

      case T_FkConstraint:
        /* should never happen. If it does fix gram.y */
        elog(ERROR, "node type %d not supported for vlabel",
             (int)nodeTag(element));
        break;

      default:
        elog(ERROR, "unrecognized node type: %d", (int)nodeTag(element));
        break;
    }
  }

  /*
   * transformIndexConstraints wants cxt.alist to contain only index
   * statements, so transfer anything we already have into extras_after
   * immediately.
   */
  *extras_after = list_concat(cxt.alist, *extras_after);
  cxt.alist = NIL;

  /*
   * Output results.
   */
  q = makeNode(Query);
  q->commandType = CMD_UTILITY;
  q->utilityStmt = (Node *)stmt;
  stmt->tableElts = cxt.columns;
  stmt->constraints = cxt.ixconstraints;
  *extras_before = list_concat(*extras_before, cxt.blist);
  *extras_after = list_concat(cxt.alist, *extras_after);
  return q;
}

static Query *transformCreateGraphStmt(ParseState *pstate,
                                          CreateGraphStmt *stmt,
                                          List **extras_before,
                                          List **extras_after) {
  CreateStmtContext cxt;
  Query *q;

  cxt.stmtType = "CREATE GRAPH";
  cxt.relation = stmt->graph;
  cxt.hasoids = false;
  cxt.isalter = false;
  cxt.columns = NIL;
  cxt.ckconstraints = NIL;
  cxt.fkconstraints = NIL;
  cxt.ixconstraints = NIL;
  cxt.inh_indexes = NIL;
  cxt.pkey = NULL;

  cxt.blist = NIL;
  cxt.alist = NIL;


  /*
   * transformIndexConstraints wants cxt.alist to contain only index
   * statements, so transfer anything we already have into extras_after
   * immediately.
   */
  *extras_after = list_concat(cxt.alist, *extras_after);
  cxt.alist = NIL;

  /*
   * Output results.
   */
  q = makeNode(Query);
  q->commandType = CMD_UTILITY;
  q->utilityStmt = (Node *)stmt;
  *extras_before = list_concat(*extras_before, cxt.blist);
  *extras_after = list_concat(cxt.alist, *extras_after);
  return q;
}

Query *transformCreateExternalStmtImpl(ParseState *pstate,
                                          CreateExternalStmt *stmt,
                                          List **extras_before,
                                          List **extras_after) {
  return transformCreateExternalStmt(pstate, stmt, extras_before, extras_after);
}

static Query *transformCreateExternalStmt(ParseState *pstate,
                                          CreateExternalStmt *stmt,
                                          List **extras_before,
                                          List **extras_after) {
  CreateStmtContext cxt;
  Query *q;
  ListCell *elements;
  ExtTableTypeDesc *desc = NULL;
  List *likeDistributedBy = NIL;
  bool bQuiet = false; /* shut up transformDistributedBy messages */
  bool onmaster = false;
  bool iswritable = stmt->iswritable;
  bool isPluggableStorage = false;
  if (!stmt->forceCreateDir) stmt->forceCreateDir = stmt->iswritable;

  cxt.stmtType = "CREATE EXTERNAL TABLE";
  cxt.isExternalTable = true;
  cxt.relation = stmt->base.relation;
  cxt.inhRelations = stmt->base.inhRelations;
  cxt.isaddpart = stmt->base.is_add_part;
  cxt.iswritable = stmt->iswritable;
  cxt.exttypedesc = stmt->exttypedesc;
  cxt.format = stmt->format;
  cxt.parentPath = stmt->parentPath;
  cxt.hasoids = false;
  cxt.isalter = false;
  cxt.columns = NIL;
  cxt.ckconstraints = NIL;
  cxt.fkconstraints = NIL;
  cxt.ixconstraints = NIL;
  cxt.inh_indexes = NIL;
  cxt.pkey = NULL;

  cxt.blist = NIL;
  cxt.alist = NIL;

  /*
   * Build type description of internal table in pluggable storage
   * framework based on format
   */
  desc = (ExtTableTypeDesc *)(stmt->exttypedesc);
  if (desc->exttabletype == EXTTBL_TYPE_UNKNOWN) {
    if (stmt->format == NULL) {
      ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
                      errmsg("Internal table must have format specification"),
                      errhint("Use CREATE TABLE FORMAT instead"),
                      errOmitLocation(true)));
    }

    /* text, csv on hdfs */
    if (pg_strncasecmp(stmt->format, "text", strlen("text")) == 0 ||
             pg_strncasecmp(stmt->format, "csv", strlen("csv")) == 0) {
      desc->exttabletype = EXTTBL_TYPE_LOCATION;
      desc->location_list = NIL;
      // desc->location_list = list_make1((Node *) makeString(PROTOCOL_HDFS));
      desc->command_string = NULL;
      desc->on_clause = NIL;
    } else {
      ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
                      errmsg("Format \"%s\" for internal table is invalid",
                             stmt->format)));
    }
    isPluggableStorage = true;
  }

  if (desc->exttabletype == EXTTBL_TYPE_LOCATION) {
    /* magma format in magma */
    if (pg_strncasecmp(stmt->format, "magma", strlen("magma")) == 0) {
      desc->exttabletype = EXTTBL_TYPE_MAGMA;
      isPluggableStorage = true;
    }

    ListCell *loc_cell = list_head(desc->location_list);
    if (loc_cell == NIL) {
      if (pg_strncasecmp(stmt->format, "orc", strlen("orc")) &&
          pg_strncasecmp(stmt->format, "text", strlen("text")) &&
          pg_strncasecmp(stmt->format, "csv", strlen("csv")) &&
		  pg_strncasecmp(stmt->format, "magmatp", strlen("magmatp")) &&
		  pg_strncasecmp(stmt->format, "magmaap", strlen("magmaap"))) {
        ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
                        errmsg(
                            "Internal table on hdfs must be \'orc\', "
                            "\'text\', \'magmatp\', \'magmaap\' or \'csv\' format")));
      }
      isPluggableStorage = true;
    } else {
      Value *loc_val = lfirst(loc_cell);
      char *loc_str = pstrdup(loc_val->val.str);
      bool is_hive_protocol = IS_HIVE_URI(loc_str);
      bool is_magma_protocol = IS_MAGMA_URI(loc_str);
      bool is_hdfs_protocol = IS_HDFS_URI(loc_str);
      isPluggableStorage = is_hdfs_protocol || is_magma_protocol || is_hive_protocol;

      if (is_hive_protocol &&
          (pg_strncasecmp(stmt->format, "orc", strlen("orc")) &&
           pg_strncasecmp(stmt->format, "csv", strlen("csv")) &&
           pg_strncasecmp(stmt->format, "text", strlen("text")))) {
        ereport(
            ERROR,
            (errcode(ERRCODE_SYNTAX_ERROR),
             errmsg(
                 "LOCATION using hive url \'%s\' does not "
                 "support \'%s\' format",
                 loc_str, stmt->format),
             errhint("Use \"FORMAT \'orc\', \'text\', or \'csv\'\" instead"),
             errOmitLocation(true)));
      }
      if (is_magma_protocol &&
          pg_strncasecmp(stmt->format, "magma", strlen("magma"))) {
        ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
                        errmsg(
                            "LOCATION using magma url \'%s\' does not "
                            "support \'%s\' format",
                            loc_str, stmt->format),
                        errhint("Use \"FORMAT \'magma\'\" instead"),
                        errOmitLocation(true)));
      }

      if (is_hdfs_protocol &&
          (pg_strncasecmp(stmt->format, "orc", strlen("orc")) &&
           pg_strncasecmp(stmt->format, "text", strlen("text")) &&
           pg_strncasecmp(stmt->format, "csv", strlen("csv")))) {
        ereport(
            ERROR,
            (errcode(ERRCODE_SYNTAX_ERROR),
             errmsg(
                 "LOCATION using hdfs url \'%s\' does not "
                 "support \'%s\' format",
                 loc_str, stmt->format),
             errhint("Use \"FORMAT \'orc\', \'text\', or \'csv\'\" instead"),
             errOmitLocation(true)));
      }
    }
  }

  // handle error table for text/csv pluggable storage
  if (stmt->sreh && isPluggableStorage &&
      (strcasecmp(stmt->format, "text") == 0 ||
       strcasecmp(stmt->format, "csv") == 0)) {
    SingleRowErrorDesc *errDesc = (SingleRowErrorDesc *)stmt->sreh;

    if (!errDesc->is_limit_in_rows) {
      ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
                      errmsg(
                          "Single row error handling with percentage limit is "
                          "not accepted for pluggable storage")));
    }

    errDesc->is_hdfs_protocol_text = true;
    if (errDesc->errtable) {
      errDesc->hdfsLoc = (char *)palloc0(MAXPGPATH);
      char *fileSpacePath = NULL;
      GetFilespacePathForTablespace(get_database_dts(MyDatabaseId),
                                    &fileSpacePath);
      // in magma case, database has no default filespace path, use dfs_url
      // instead for error table.
      fileSpacePath = fileSpacePath == NULL ? getDefaultFilespace()
                                          : fileSpacePath;
      uuid_t uuid;
      char buf[1024];
      uuid_generate(uuid);
      uuid_unparse(uuid, buf);
      sprintf(errDesc->hdfsLoc, "%s/ExtErrTbl/%s", fileSpacePath, buf);
      if (fileSpacePath)
      {
        pfree(fileSpacePath);
      }
    }
  }

  // Only on top-most partitioned tables
  if (stmt->base.partitionBy && !stmt->base.is_part_child) {
    if (isPluggableStorage)
      fixCreateStmtForPartitionedTable(&stmt->base);
    else
      elog(ERROR,
           "Partition external table only supported for pluggable storage");
  }

  /*
   * Run through each primary element in the table creation clause. Separate
   * column defs from constraints, and do preliminary analysis.
   */
  foreach (elements, stmt->base.tableElts) {
    Node *element = lfirst(elements);

    switch (nodeTag(element)) {
      case T_ColumnDef:
        transformColumnDefinition(pstate, &cxt, (ColumnDef *)element);
        break;

      case T_Constraint:
        transformExtTableConstraint(pstate, &cxt, (Constraint *)element);
        break;

      case T_FkConstraint:
        /* should never happen. If it does fix gram.y */
        elog(ERROR, "node type %d not supported for external tables",
             (int)nodeTag(element));
        break;

      case T_InhRelation: {
        /* LIKE */
        bool isBeginning = (cxt.columns == NIL);

        transformInhRelation(pstate, &cxt, (InhRelation *)element,
                             !isPluggableStorage);

        if (Gp_role == GP_ROLE_DISPATCH && isBeginning &&
            stmt->base.distributedBy == NIL && stmt->policy == NULL &&
            iswritable /* dont bother if readable table */) {
          likeDistributedBy = getLikeDistributionPolicy((InhRelation *)element);
        }
      } break;

      case T_String:
        break;

      default:
        elog(ERROR, "unrecognized node type: %d", (int)nodeTag(element));
        break;
    }
  }

  /*
   * transformIndexConstraints wants cxt.alist to contain only index
   * statements, so transfer anything we already have into extras_after
   * immediately.
   */
  *extras_after = list_concat(cxt.alist, *extras_after);
  cxt.alist = NIL;

  /*
   * Postprocess constraints that give rise to index definitions.
   */
  transformIndexConstraints(pstate, &cxt, false);

  /*
   * Check if this is an EXECUTE ON MASTER table. We'll need this information
   * in transformExternalDistributedBy. While at it, we also check if an error
   * table is attempted to be used on ON MASTER table and error if so.
   */
  if (!iswritable) {
    desc = (ExtTableTypeDesc *)stmt->exttypedesc;

    if (desc->exttabletype == EXTTBL_TYPE_EXECUTE) {
      ListCell *exec_location_opt;

      foreach (exec_location_opt, desc->on_clause) {
        DefElem *defel = (DefElem *)lfirst(exec_location_opt);

        if (strcmp(defel->defname, "master") == 0) {
          SingleRowErrorDesc *srehDesc = (SingleRowErrorDesc *)stmt->sreh;

          onmaster = true;

          if (srehDesc && srehDesc->errtable)
            ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                            errmsg(
                                "External web table with ON MASTER clause "
                                "cannot use error tables.")));
        }
      }
    }
  }

  /*
   * Check if we need to create an error table. If so, add it to the
   * before list.
   */
  if (stmt->sreh && ((SingleRowErrorDesc *)stmt->sreh)->errtable)
    transformSingleRowErrorHandling(pstate, &cxt,
                                    (SingleRowErrorDesc *)stmt->sreh);

  transformETDistributedBy(pstate, &cxt, &(stmt->base.distributedBy),
                           &stmt->policy, NULL, /*no WITH options for ET*/
                           likeDistributedBy, bQuiet, iswritable, onmaster);

  // Process table partitioning clause
  if (isPluggableStorage)
    transformPartitionBy(pstate, &cxt, &stmt->base, stmt->base.partitionBy,
                         stmt->policy);

  /*
   * Output results.
   */
  q = makeNode(Query);
  q->commandType = CMD_UTILITY;
  q->utilityStmt = (Node *)stmt;
  stmt->base.tableElts = cxt.columns;
  stmt->base.constraints = cxt.ckconstraints;
  stmt->pkey = cxt.pkey;
  *extras_before = list_concat(*extras_before, cxt.blist);
  *extras_after = list_concat(cxt.alist, *extras_after);

  return q;
}

static Query *transformCreateForeignStmt(ParseState *pstate,
                                         CreateForeignStmt *stmt,
                                         List **extras_before,
                                         List **extras_after) {
  CreateStmtContext cxt;
  Query *q;
  ListCell *elements;

  cxt.stmtType = "CREATE FOREIGN TABLE";
  cxt.isExternalTable = false;
  cxt.relation = stmt->relation;
  cxt.inhRelations = NIL;
  cxt.hasoids = false;
  cxt.isalter = false;
  cxt.columns = NIL;
  cxt.ckconstraints = NIL;
  cxt.fkconstraints = NIL;
  cxt.ixconstraints = NIL;
  cxt.pkey = NULL;

  cxt.blist = NIL;
  cxt.alist = NIL;

  /*
   * Run through each primary element in the table creation clause. Separate
   * column defs from constraints, and do preliminary analysis.
   */
  foreach (elements, stmt->tableElts) {
    Node *element = lfirst(elements);

    switch (nodeTag(element)) {
      case T_ColumnDef:
        transformColumnDefinition(pstate, &cxt, (ColumnDef *)element);
        break;

      case T_Constraint:
      case T_FkConstraint:
        /* should never happen. If it does fix gram.y */
        elog(ERROR, "node type %d not supported for foreign tables",
             (int)nodeTag(element));
        break;

      case T_InhRelation: {
        /* LIKE */
        transformInhRelation(pstate, &cxt, (InhRelation *)element, true);
      } break;

      default:
        elog(ERROR, "unrecognized node type: %d", (int)nodeTag(element));
        break;
    }
  }

  Assert(cxt.ckconstraints == NIL);
  Assert(cxt.fkconstraints == NIL);
  Assert(cxt.ixconstraints == NIL);

  /*
   * Output results.
   */
  q = makeNode(Query);
  q->commandType = CMD_UTILITY;
  q->utilityStmt = (Node *)stmt;
  stmt->tableElts = cxt.columns;
  *extras_before = list_concat(*extras_before, cxt.blist);
  *extras_after = list_concat(cxt.alist, *extras_after);

  return q;
}

static void transformColumnDefinition(ParseState *pstate,
                                      CreateStmtContext *cxt,
                                      ColumnDef *column) {
  bool is_serial;
  bool saw_nullable;
  bool saw_default;
  Constraint *constraint;
  ListCell *clist;

  cxt->columns = lappend(cxt->columns, column);

  /* Check for SERIAL pseudo-types */
  is_serial = false;
  if (list_length(column->typname->names) == 1) {
    char *typname = strVal(linitial(column->typname->names));

    if (strcmp(typname, "serial") == 0 || strcmp(typname, "serial4") == 0) {
      is_serial = true;
      column->typname->names = NIL;
      column->typname->typid = INT4OID;
    } else if (strcmp(typname, "bigserial") == 0 ||
               strcmp(typname, "serial8") == 0) {
      is_serial = true;
      column->typname->names = NIL;
      column->typname->typid = INT8OID;
    }
  }

  /* Do necessary work on the column type declaration */
  transformColumnType(pstate, column);

  /* Special actions for SERIAL pseudo-types */
  if (is_serial) {
    Oid snamespaceid;
    char *snamespace;
    char *sname;
    char *qstring;
    A_Const *snamenode;
    FuncCall *funccallnode;
    CreateSeqStmt *seqstmt;
    AlterSeqStmt *altseqstmt;
    List *attnamelist;

    /*
     * Determine namespace and name to use for the sequence.
     *
     * Although we use ChooseRelationName, it's not guaranteed that the
     * selected sequence name won't conflict; given sufficiently long
     * field names, two different serial columns in the same table could
     * be assigned the same sequence name, and we'd not notice since we
     * aren't creating the sequence quite yet.  In practice this seems
     * quite unlikely to be a problem, especially since few people would
     * need two serial columns in one table.
     */
    snamespaceid = RangeVarGetCreationNamespace(cxt->relation);
    snamespace = get_namespace_name(snamespaceid);
    sname = ChooseRelationName(cxt->relation->relname, column->colname, "seq",
                               snamespaceid, NULL);

    ereport(NOTICE, (errmsg(
                        "%s will create implicit sequence \"%s\" for serial "
                        "column \"%s.%s\"",
                        cxt->stmtType, sname, cxt->relation->relname,
                        column->colname)));

    /*
     * Build a CREATE SEQUENCE command to create the sequence object, and
     * add it to the list of things to be done before this CREATE/ALTER
     * TABLE.
     */
    seqstmt = makeNode(CreateSeqStmt);
    seqstmt->sequence =
        makeRangeVar(NULL /*catalogname*/, snamespace, sname, -1);
    seqstmt->sequence->istemp = cxt->relation->istemp;
    seqstmt->options = NIL;

    cxt->blist = lappend(cxt->blist, seqstmt);

    /*
     * Build an ALTER SEQUENCE ... OWNED BY command to mark the sequence
     * as owned by this column, and add it to the list of things to be
     * done after this CREATE/ALTER TABLE.
     */
    altseqstmt = makeNode(AlterSeqStmt);
    altseqstmt->sequence =
        makeRangeVar(NULL /*catalogname*/, snamespace, sname, -1);
    attnamelist =
        list_make3(makeString(snamespace), makeString(cxt->relation->relname),
                   makeString(column->colname));
    altseqstmt->options =
        list_make1(makeDefElem("owned_by", (Node *)attnamelist));

    cxt->alist = lappend(cxt->alist, altseqstmt);

    /*
     * Create appropriate constraints for SERIAL.  We do this in full,
     * rather than shortcutting, so that we will detect any conflicting
     * constraints the user wrote (like a different DEFAULT).
     *
     * Create an expression tree representing the function call
     * nextval('sequencename').  We cannot reduce the raw tree to cooked
     * form until after the sequence is created, but there's no need to do
     * so.
     */
    qstring = quote_qualified_identifier(snamespace, sname);
    snamenode = makeNode(A_Const);
    snamenode->val.type = T_String;
    snamenode->val.val.str = qstring;
    snamenode->typname = SystemTypeName("regclass");
    snamenode->location = -1; /*CDB*/
    funccallnode = makeNode(FuncCall);
    funccallnode->funcname = SystemFuncName("nextval");
    funccallnode->args = list_make1(snamenode);
    funccallnode->agg_star = false;
    funccallnode->agg_distinct = false;
    funccallnode->func_variadic = false;
    funccallnode->location = -1;

    constraint = makeNode(Constraint);
    constraint->contype = CONSTR_DEFAULT;
    constraint->raw_expr = (Node *)funccallnode;
    constraint->cooked_expr = NULL;
    constraint->keys = NIL;
    column->constraints = lappend(column->constraints, constraint);

    constraint = makeNode(Constraint);
    constraint->contype = CONSTR_NOTNULL;
    column->constraints = lappend(column->constraints, constraint);
  }

  /* Process column constraints, if any... */
  transformConstraintAttrs(column->constraints);

  saw_nullable = false;
  saw_default = false;

  foreach (clist, column->constraints) {
    constraint = lfirst(clist);

    /*
     * If this column constraint is a FOREIGN KEY constraint, then we fill
     * in the current attribute's name and throw it into the list of FK
     * constraints to be processed later.
     */
    if (IsA(constraint, FkConstraint)) {
      FkConstraint *fkconstraint = (FkConstraint *)constraint;

      fkconstraint->fk_attrs = list_make1(makeString(column->colname));
      cxt->fkconstraints = lappend(cxt->fkconstraints, fkconstraint);
      continue;
    }

    Assert(IsA(constraint, Constraint));

    switch (constraint->contype) {
      case CONSTR_NULL:
        if (saw_nullable && column->is_not_null)
          ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
                          errmsg(
                              "conflicting NULL/NOT NULL declarations for "
                              "column \"%s\" of table \"%s\"",
                              column->colname, cxt->relation->relname)));
        column->is_not_null = FALSE;
        saw_nullable = true;
        break;

      case CONSTR_NOTNULL:
        if (saw_nullable && !column->is_not_null)
          ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
                          errmsg(
                              "conflicting NULL/NOT NULL declarations for "
                              "column \"%s\" of table \"%s\"",
                              column->colname, cxt->relation->relname)));
        column->is_not_null = TRUE;
        saw_nullable = true;
        break;

      case CONSTR_DEFAULT:
        if (saw_default)
          ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
                          errmsg(
                              "multiple default values specified for column "
                              "\"%s\" of table \"%s\"",
                              column->colname, cxt->relation->relname)));
        /*
         * Note: DEFAULT NULL maps to constraint->raw_expr == NULL
         *
         * We lose the knowledge that the user specified DEFAULT NULL at
         * this point, so we record it in default_is_null
         */
        column->raw_default = constraint->raw_expr;
        column->default_is_null = !constraint->raw_expr;
        Assert(constraint->cooked_expr == NULL);
        saw_default = true;
        break;

      case CONSTR_PRIMARY:
      case CONSTR_UNIQUE:
        if (constraint->keys == NIL)
          constraint->keys = list_make1(makeString(column->colname));
        cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
        break;

      case CONSTR_CHECK:
        cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
        break;

      case CONSTR_ATTR_DEFERRABLE:
      case CONSTR_ATTR_NOT_DEFERRABLE:
      case CONSTR_ATTR_DEFERRED:
      case CONSTR_ATTR_IMMEDIATE:
        /* transformConstraintAttrs took care of these */
        break;

      default:
        elog(ERROR, "unrecognized constraint type: %d", constraint->contype);
        break;
    }
  }
}

static void transformTableConstraint(ParseState *pstate, CreateStmtContext *cxt,
                                     Constraint *constraint) {
  switch (constraint->contype) {
    case CONSTR_PRIMARY:
    case CONSTR_UNIQUE:
      cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
      break;

    case CONSTR_CHECK:
      cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
      break;

    case CONSTR_NULL:
    case CONSTR_NOTNULL:
    case CONSTR_DEFAULT:
    case CONSTR_ATTR_DEFERRABLE:
    case CONSTR_ATTR_NOT_DEFERRABLE:
    case CONSTR_ATTR_DEFERRED:
    case CONSTR_ATTR_IMMEDIATE:
      elog(ERROR, "invalid context for constraint type %d",
           constraint->contype);
      break;

    default:
      elog(ERROR, "unrecognized constraint type: %d", constraint->contype);
      break;
  }
}

static void transformExtTableConstraint(ParseState *pstate,
                                        CreateStmtContext *cxt,
                                        Constraint *constraint) {
  switch (constraint->contype) {
    case CONSTR_PRIMARY:
      cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
      break;

    case CONSTR_CHECK:
      cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
      break;

    default:
      elog(ERROR, "unrecognized constraint type: %d", constraint->contype);
      break;
  }
}

/*
 * transformETDistributedBy - transform DISTRIBUTED BY clause for
 * external tables.
 *
 * WET: by default we distribute RANDOMLY, or by the distribution key
 * of the LIKE table if exists. However, if DISTRIBUTED BY was
 * specified we use it by calling the regular transformDistributedBy and
 * handle it like we would for non external tables.
 *
 * RET: We always create a random distribution policy entry *unless*
 * this is an EXECUTE table with ON MASTER specified, in which case
 * we create no policy so that the master will be accessed.
 */
static void transformETDistributedBy(ParseState *pstate, CreateStmtContext *cxt,
                                     List **distributedBy, GpPolicy **policyp,
                                     List *options, List *likeDistributedBy,
                                     bool bQuiet, bool iswritable,
                                     bool onmaster) {
  int maxattrs = 200;
  GpPolicy *p = NULL;

  /*
   * utility mode creates can't have a policy.  Only the QD can have policies
   */
  if (Gp_role != GP_ROLE_DISPATCH) {
    *policyp = NULL;
    return;
  }

  if (!iswritable && list_length(*distributedBy) > 0)
    ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                    errmsg(
                        "Readable external tables can\'t specify a DISTRIBUTED "
                        "BY clause.")));

  if (iswritable) {
    /* WET */

		if ((pg_strcasecmp(cxt->format, "magmaap") == 0
				|| pg_strcasecmp(cxt->format, "magmatp") == 0)
				&& cxt->columns)  // support magma table with no column
		{/* defaults to use first column as distribution key with magma table*/
			p = (GpPolicy *) palloc(
					sizeof(GpPolicy) + maxattrs * sizeof(p->attrs[0]));
			p->ptype = POLICYTYPE_PARTITIONED;
			// distributed randomly give a NULL node to the list
			// no distributed by clause or distributed randomly both use first column
			// as distributed key for magma table
			if (*distributedBy != NIL
					&& lfirst(list_head(*distributedBy)) == NULL)
			{
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg( "magmatp or "
						    "magmaap table do not support \"distributed randomly\" yet.")));
			}

			if (*distributedBy == NIL)
			{
				ColumnDef *first_column = (ColumnDef *) linitial(cxt->columns);
				char *attname = pstrdup(first_column->colname);

				*distributedBy = lappend(*distributedBy,
						(Node *) makeString(attname));
				assert(list_length(*distributedBy) == 1);
				p->nattrs = 1;
				p->attrs[0] = 1;
			}

			/* regular DISTRIBUTED BY transformation */
			transformDistributedBy(pstate, cxt, *distributedBy, &p, options,
					likeDistributedBy, bQuiet);

			p->bucketnum = GetDefaultMagmaBucketNum();
			*policyp = p;
			return;
		}

    if (*distributedBy == NIL && likeDistributedBy == NIL) {
      /* defaults to DISTRIBUTED RANDOMLY */
      p = (GpPolicy *)palloc(sizeof(GpPolicy) + maxattrs * sizeof(p->attrs[0]));
      p->ptype = POLICYTYPE_PARTITIONED;
      p->nattrs = 0;
      p->bucketnum = GetRelOpt_bucket_num_fromOptions(
          options, GetExternalTablePartitionNum());
      p->attrs[0] = 1;

      *policyp = p;
    } else {
      /* regular DISTRIBUTED BY transformation */
      transformDistributedBy(pstate, cxt, *distributedBy, policyp, options,
                             likeDistributedBy, bQuiet);
    }
  } else {
    /* RET */

    if (onmaster) {
      p = NULL;
    } else {
      /* defaults to DISTRIBUTED RANDOMLY */
      p = (GpPolicy *)palloc(sizeof(GpPolicy) + maxattrs * sizeof(p->attrs[0]));
      p->ptype = POLICYTYPE_PARTITIONED;
      p->nattrs = 0;
      p->bucketnum = GetRelOpt_bucket_num_fromOptions(
          options, GetExternalTablePartitionNum());
      p->attrs[0] = 1;
    }

    *policyp = p;
  }
}

/****************stmt->policy*********************/
static void transformDistributedBy(ParseState *pstate, CreateStmtContext *cxt,
                                   List *distributedBy, GpPolicy **policyp,
                                   List *options, List *likeDistributedBy,
                                   bool bQuiet) {
  ListCell *keys = NULL;
  GpPolicy *policy = NULL;
  int colindex = 0;
  int maxattrs = 200;

  /*
   * utility mode creates can't have a policy.  Only the QD can have policies
   *
   */
  if (Gp_role != GP_ROLE_DISPATCH) {
    *policyp = NULL;
    return;
  }
  
  /*
   * Currently heap table only exists in hawq's master, so there is no
   * policy information.
   */
  if (enable_heap_table_on_master) {
    bool appendonly;
    bool hasAppendOnly = GetRelOpt_appendonly_fromOptions(options, &appendonly);
    if (hasAppendOnly && !appendonly) {
      *policyp = NULL;
      return;
    }
  }
  
  policy = (GpPolicy *)palloc(sizeof(GpPolicy) +
                              maxattrs * sizeof(policy->attrs[0]));
  policy->ptype = POLICYTYPE_PARTITIONED;
  policy->nattrs = 0;
  policy->bucketnum = -1;
  policy->attrs[0] = 1;

  /*
   * If new table INHERITS from one or more parent tables, check parents.
   */
  if (cxt->inhRelations != NIL) {
    ListCell *entry;

    bool isMagmaTable = false;
    if (cxt->exttypedesc && cxt->exttypedesc->type == EXTTBL_TYPE_MAGMA) {
      isMagmaTable = true;
    }
    foreach (entry, cxt->inhRelations) {
      RangeVar *parent = (RangeVar *)lfirst(entry);
      Oid relId = RangeVarGetRelid(parent, false, false /*allowHcatalog*/);
      bool isOldTableMagma = dataStoredInMagmaByOid(relId);
      GpPolicy *oldTablePolicy = GpPolicyFetch(CurrentMemoryContext, relId);

      /* Partitioned child must have partitioned parents. */
      if (oldTablePolicy == NULL ||
          oldTablePolicy->ptype != POLICYTYPE_PARTITIONED) {
        ereport(ERROR, (errcode(ERRCODE_GP_FEATURE_NOT_SUPPORTED),
                        errmsg(
                            "cannot inherit from catalog table \"%s\" "
                            "to create table \"%s\".",
                            parent->relname, cxt->relation->relname),
                        errdetail(
                            "An inheritance hierarchy cannot contain a "
                            "mixture of distributed and "
                            "non-distributed tables.")));
      }
      /*
       * If we still don't know what distribution to use, and this
       * is an inherited table, set the distribution based on the
       * parent (or one of the parents)
       */
      if (distributedBy == NIL && oldTablePolicy->nattrs >= 0) {
        int ia;

        if (oldTablePolicy->nattrs > 0) {
          for (ia = 0; ia < oldTablePolicy->nattrs; ia++) {
            char *attname = get_attname(relId, oldTablePolicy->attrs[ia]);

            distributedBy = lappend(distributedBy, (Node *)makeString(attname));
          }
        } else {
          /* strewn parent */
          distributedBy = lappend(distributedBy, (Node *)NULL);
        }

        if (options != NIL)
          policy->bucketnum = GetRelOpt_bucket_num_fromOptions(options, -1);

        /*
         * If child table is not magma table and parent table is magma table, do not
         * inherit the bucketnum of parent table, use value of default_hash_table_bucket_number
         * instead.
         */
        if (policy->bucketnum == -1)
        {
          if (isOldTableMagma && !isMagmaTable)
          {
            policy->bucketnum = default_hash_table_bucket_number;
          }
          else
          {
            policy->bucketnum = oldTablePolicy->bucketnum;
          }
        }
        if (!bQuiet)
          elog(NOTICE,
               "Table has parent, setting distribution columns "
               "to match parent table");
      }
      pfree(oldTablePolicy);
    }
  }

  if (distributedBy == NIL && likeDistributedBy != NIL) {
    distributedBy = likeDistributedBy;
    if (!bQuiet)
      elog(NOTICE,
           "Table doesn't have 'distributed by' clause, "
           "defaulting to distribution columns from LIKE table");
  }

  if (distributedBy == NIL) {
    /*
     * if we get here, we haven't a clue what to use for the distribution
* columns.
* Distributed by randomly.
     */
    policy->nattrs = 0;
    policy->bucketnum = -1;
  } else {
    /*
     * We have a DISTRIBUTED BY column list, either specified by the user
     * or defaulted to like table or inherit parent table . Process it now and
     * set the distribution policy.
     */
    policy->nattrs = 0;
    if (!(distributedBy->length == 1 && linitial(distributedBy) == NULL)) {
      foreach (keys, distributedBy) {
        char *key = strVal(lfirst(keys));
        bool found = false;
        ColumnDef *column = NULL;
        ListCell *columns;

        colindex = 0;

        if (cxt->inhRelations) {
          /* try inherited tables */
          ListCell *inher;

          foreach (inher, cxt->inhRelations) {
            RangeVar *inh = (RangeVar *)lfirst(inher);
            Relation rel;
            int count;

            Assert(IsA(inh, RangeVar));
            rel = heap_openrv(inh, AccessShareLock);
            if (rel->rd_rel->relkind != RELKIND_RELATION)
              ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                              errmsg("inherited relation \"%s\" is not a table",
                                     inh->relname)));
            for (count = 0; count < rel->rd_att->natts; count++) {
              Form_pg_attribute inhattr = rel->rd_att->attrs[count];
              char *inhname = NameStr(inhattr->attname);

              if (inhattr->attisdropped) continue;
              colindex++;
              if (strcmp(key, inhname) == 0) {
                found = true;

                break;
              }
            }
            heap_close(rel, NoLock);
            if (found) {
              elog(
                  DEBUG1,
                  "DISTRIBUTED BY clause refers to columns of inherited table");
              break;
            }
          }
        }

        if (!found) {
          foreach (columns, cxt->columns) {
            column = (ColumnDef *)lfirst(columns);
            Assert(IsA(column, ColumnDef));
            colindex++;

            if (strcmp(column->colname, key) == 0) {
              Oid typeOid = typenameTypeId(NULL, column->typname);

              /*
               * To be a part of a distribution key, this type must
               * be supported for hashing internally in Greenplum
               * Database. We check if the base type is supported
               * for hashing or if it is an array type (we support
               * hashing on all array types).
               */
              if (!isGreenplumDbHashable(typeOid)) {
                ereport(ERROR, (errcode(ERRCODE_GP_FEATURE_NOT_SUPPORTED),
                                errmsg(
                                    "type \"%s\" can't be a part of a "
                                    "distribution key",
                                    format_type_be(typeOid))));
              }

              found = true;
              break;
            }
          }
        }

        /*
        * In the ALTER TABLE case, don't complain about index keys
        * not created in the command; they may well exist already.
        * DefineIndex will complain about them if not, and will also
        * take care of marking them NOT NULL.
        */
        if (!found && !cxt->isalter)
          ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN),
                          errmsg(
                              "column \"%s\" named in 'DISTRIBUTED BY' clause "
                              "does not exist",
                              key),
                          errOmitLocation(true)));

        policy->attrs[policy->nattrs++] = colindex;
      }

      /* MPP-14770: we should check for duplicate column usage */
      foreach (keys, distributedBy) {
        char *key = strVal(lfirst(keys));

        ListCell *lkeys = NULL;
        for_each_cell(lkeys, keys->next) {
          char *lkey = strVal(lfirst(lkeys));
          if (strcmp(key, lkey) == 0)
            ereport(ERROR,
                    (errcode(ERRCODE_DUPLICATE_COLUMN),
                     errmsg("duplicate column \"%s\" in DISTRIBUTED BY clause",
                            key)));
        }
      }
      if ((policy->nattrs > 0) && (policy->bucketnum == -1)) {
        policy->bucketnum = GetRelOpt_bucket_num_fromOptions(
            options, GetHashDistPartitionNum());
      }
    }
  }

  if (policy->bucketnum == -1) {
    policy->bucketnum =
        GetRelOpt_bucket_num_fromOptions(options, GetDefaultPartitionNum());
  }

  *policyp = policy;

  if (cxt && cxt->inhRelations) {
    ListCell *entry;

    foreach (entry, cxt->inhRelations) {
      RangeVar *parent = (RangeVar *)lfirst(entry);
      Oid relId = RangeVarGetRelid(parent, false, false /*allowHcatalog*/);
      GpPolicy *parentPolicy = GpPolicyFetch(CurrentMemoryContext, relId);

      if (!GpPolicyEqual(policy, parentPolicy)) {
        ereport(ERROR, (errcode(ERRCODE_GP_FEATURE_NOT_SUPPORTED),
                        errmsg(
                            "distribution policy for \"%s\" "
                            "must be the same as that for \"%s\"",
                            cxt->relation->relname, parent->relname)));
      }
    }
  }

  if (cxt && cxt->pkey) /* Primary key	specified.	Make sure
                                                 * distribution columns match */
  {
    int i = 0;
    IndexStmt *index = cxt->pkey;
    List *indexParams = index->indexParams;
    ListCell *ip;

    if(list_length(indexParams) < policy->nattrs)
    {
      ereport(ERROR,
                  (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                  errmsg(
                       "PRIMARY KEY and DISTRIBUTED BY definitions incompatible"),
                  errhint("DISTRIBUTED KEY columns must be equal to or a "
                           "left-subset of PRIMARY KEY columns, the default "
                           "DISTRIBUTED KEY is first column."),
                  errOmitLocation(true)));
    }

    foreach (ip, indexParams) {
      IndexElem *iparam;

      if (i >= policy->nattrs) break;

      iparam = lfirst(ip);
      if (iparam->name != 0) {
        bool found = false;
        ColumnDef *column = NULL;
        ListCell *columns;

        colindex = 0;

        if (cxt->inhRelations) {
          /* try inherited tables */
          ListCell *inher;

          foreach (inher, cxt->inhRelations) {
            RangeVar *inh = (RangeVar *)lfirst(inher);
            Relation rel;
            int count;

            Assert(IsA(inh, RangeVar));
            rel = heap_openrv(inh, AccessShareLock);
            if (rel->rd_rel->relkind != RELKIND_RELATION)
              ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                              errmsg("inherited relation \"%s\" is not a table",
                                     inh->relname)));
            for (count = 0; count < rel->rd_att->natts; count++) {
              Form_pg_attribute inhattr = rel->rd_att->attrs[count];
              char *inhname = NameStr(inhattr->attname);

              if (inhattr->attisdropped) continue;
              colindex++;

              if (strcmp(iparam->name, inhname) == 0) {
                found = true;
                break;
              }
            }
            heap_close(rel, NoLock);

            if (found)
              elog(DEBUG1,
                   "'distributed by' clause refers to "
                   "columns of inherited table");

            if (found) break;
          }
        }

        if (!found) {
          foreach (columns, cxt->columns) {
            column = (ColumnDef *)lfirst(columns);
            Assert(IsA(column, ColumnDef));
            colindex++;
            if (strcmp(column->colname, iparam->name) == 0) {
              found = true;
              break;
            }
          }
        }
        if (colindex != policy->attrs[i]) {
          ereport(
              ERROR,
              (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
               errmsg(
                   "PRIMARY KEY and DISTRIBUTED BY definitions incompatible"),
               errhint("DISTRIBUTED KEY columns must be equal to or a "
                       "left-subset of PRIMARY KEY columns, the default "
                           "DISTRIBUTED KEY is first column."),
               errOmitLocation(true)));
        }

        i++;
      }
    }
  }
}

/*
 * Add any missing encoding attributes (compresstype = none, blocksize=...).
 */
static List *fillin_encoding(List *list) {
  bool foundCompressType = false;
  bool foundCompressTypeNone = false;
  bool snappyCompressType = false;
  char *cmplevel = NULL;
  bool need_free_cmplevel = false;
  bool foundBlockSize = false;
  char *arg;
  bool columnTable = false;

  DefElem *e1 = makeDefElem("compresstype", (Node *)makeString("none"));
  DefElem *e2 = makeDefElem("compresslevel",
                            (Node *)makeInteger(0)); /* compress level 0 */
  DefElem *e2b = makeDefElem("compresslevel",
                             (Node *)makeInteger(1)); /* compress level 1 */
  DefElem *e3 = makeDefElem("blocksize",
                            (Node *)makeInteger(DEFAULT_APPENDONLY_BLOCK_SIZE));
  DefElem *zlibComp = makeDefElem("compresstype", (Node *)makeString("zlib"));
  DefElem *gzipComp = makeDefElem("compresstype", (Node *)makeString("gzip"));

  List *retList = list;
  ListCell *lc;

  foreach (lc, list) {
    DefElem *el = lfirst(lc);

    if (pg_strcasecmp("compresstype", el->defname) == 0) {
      foundCompressType = true;
      bool need_free_arg = false;
      arg = defGetString(el, &need_free_arg);
      if (pg_strcasecmp("none", arg) == 0) foundCompressTypeNone = true;
      if (pg_strcasecmp("snappy", arg) == 0) snappyCompressType = true;
      if (need_free_arg) {
        pfree(arg);
        arg = NULL;
      }

      AssertImply(need_free_arg, NULL == arg);
    } else if (pg_strcasecmp("compresslevel", el->defname) == 0) {
      cmplevel = defGetString(el, &need_free_cmplevel);
    } else if (pg_strcasecmp("blocksize", el->defname) == 0) {
      foundBlockSize = true;
    } else if (pg_strcasecmp("orientation", el->defname) == 0) {
      bool need_free_arg = false;
      arg = defGetString(el, &need_free_arg);
      if (pg_strcasecmp("parquet", arg) == 0
          || pg_strcasecmp("orc", arg) == 0)
        columnTable = true;
      if (need_free_arg) {
        pfree(arg);
      }
    }
  }

  if (foundCompressType == false) {
    /*
     * We actually support "compresslevel=N" and default the compression
     * type to zlib (see default_reloptions()). There's no pleasant way to
     * factor out the common code. We only add zlib compression for non-zero
     * N in compresslevel=N (we check if N is meaningful to zlib later).
    */
    if (cmplevel && strcmp(cmplevel, "0") != 0) {
      if (!columnTable)
        retList = lappend(retList, zlibComp);
      else
        retList = lappend(retList, gzipComp);
    } else
      retList = lappend(retList, e1);
  }

  if (!cmplevel) {
    if (foundCompressType == false || foundCompressTypeNone == true)
      retList = lappend(retList, e2); /* no compress type or snappy compress
                                         type => compresslevel = 0 */
    else if (snappyCompressType == false)
      retList = lappend(
          retList,
          e2b); /* compress type, but no compress level => compress level = 1 */
  }

  if ((foundBlockSize == false) && (columnTable == false))
    retList = lappend(retList, e3);

  return retList;
}

static int deparse_partition_rule(Node *pNode, char *outbuf, size_t outsize) {
  if (!pNode) return 0;

  switch (nodeTag(pNode)) {
    case T_NullTest: {
      NullTest *nt = (NullTest *)pNode;
      char leftbuf[1000];
      if (!deparse_partition_rule((Node *)nt->arg, leftbuf, sizeof(leftbuf)))
        return 0;

      snprintf(outbuf, outsize, "%s %s", leftbuf,
               nt->nulltesttype == IS_NULL ? "ISNULL" : "IS NOT NULL");
    }

    break;
    case T_Value:

      /* XXX XXX XXX ??? */

      break;
    case T_ColumnRef: {
      ColumnRef *pCRef = (ColumnRef *)pNode;
      List *coldefs = pCRef->fields;
      ListCell *lc = NULL;
      StringInfoData sid;
      int colcnt = 0;

      initStringInfo(&sid);

      lc = list_head(coldefs);

      for (; lc; lc = lnext(lc)) /* for all cols */
      {
        Node *pCol = lfirst(lc);
        char leftBuf[10000];

        if (!deparse_partition_rule(pCol, leftBuf, sizeof(leftBuf))) return 0;

        if (colcnt) {
          appendStringInfo(&sid, ".");
        }

        appendStringInfo(&sid, "%s", leftBuf);

        colcnt++;
      } /* end for all cols */

      outbuf[0] = '\0';
      snprintf(outbuf, outsize, "%s", sid.data);
      pfree(sid.data);

      break;
    }
    case T_String:
      snprintf(outbuf, outsize, "%s", strVal(pNode));
      break;
    case T_Integer:
      snprintf(outbuf, outsize, "%ld", intVal(pNode));
      break;
    case T_Float:
      snprintf(outbuf, outsize, "%f", floatVal(pNode));
      break;
    case T_A_Const: {
      A_Const *acs = (A_Const *)pNode;

      if (acs->val.type == T_String) {
        if (acs->typname) /* deal with explicit types */
        {
          /* XXX XXX: simple types only -- need to
           * handle Interval, etc */

          snprintf(outbuf, outsize, "\'%s\'::%s", acs->val.val.str,
                   TypeNameToString(acs->typname));
        } else {
          snprintf(outbuf, outsize, "\'%s\'", acs->val.val.str);
        }

        return 1;
      }
      return (deparse_partition_rule((Node *)&(acs->val), outbuf, outsize));
    } break;
    case T_A_Expr: {
      A_Expr *ax = (A_Expr *)pNode;
      char leftBuf[10000];
      char rightBuf[10000];
      char *infix_op;

      switch (ax->kind) {
        case AEXPR_OP:  /* normal operator */
        case AEXPR_AND: /* booleans - name field is unused */
        case AEXPR_OR:
          break;
        default:
          return 0;
      }
      if (!deparse_partition_rule(ax->lexpr, leftBuf, sizeof(leftBuf)))
        return 0;
      if (!deparse_partition_rule(ax->rexpr, rightBuf, sizeof(rightBuf)))
        return 0;
      switch (ax->kind) {
        case AEXPR_OP: /* normal operator */
          infix_op = strVal(lfirst(list_head(ax->name)));
          break;
        case AEXPR_AND: /* booleans - name field is unused */
          infix_op = "AND";
          break;
        case AEXPR_OR:
          infix_op = "OR";
          break;
        default:
          return 0;
      }
      snprintf(outbuf, outsize, "(%s %s %s)", leftBuf, infix_op, rightBuf);

    } break;
    case T_Var: {
      /*				Var		   *var = (Var *)
       * node; */
    }
    default:
      break;
  }
  return 1;
}

#if 0
static A_Const *
make_a_const(A_Const *pValConst, char *val_str)
{
	A_Const *pNewValConst = makeNode(A_Const);
	int estat = 0;

	Assert(val_str);

	pNewValConst->val.type = pValConst->val.type;
	pNewValConst->typname = pValConst->typname;
    pNewValConst->location = pValConst->location;

	switch (pValConst->val.type)
	{
		case T_Integer:
			estat = sscanf(val_str, "%ld",
						   &(pNewValConst->val.val.ival));
			break;
		case T_Float:
/*			estat = scanf(val_str, "%f",
			(pNewValConst->val.val.str)); */

		case T_String:
			pNewValConst->val.val.str = val_str;

			estat = 1;
			break;

		default:

			break;
	}

	if (estat < 1)
	{
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
				 errmsg("unknown type or operator: %s",
						val_str)));
	}

	return (pNewValConst);
} /* end make_a_const */
#endif

static Node *make_prule_catalog(ParseState *pstate, CreateStmtContext *cxt,
                                CreateStmtBase *stmt, Node *partitionBy,
                                PartitionElem *pElem, char *at_depth,
                                char *child_name_str, char *exprBuf,
                                Node *pWhere) {
  Node *pResult = NULL;
  InsertStmt *pIns = NULL;
  RangeVar *parent_tab_name;
  RangeVar *child_tab_name;
  char ruleBuf[10000];
  char newVals[10000];

  {
    List *coldefs = stmt->tableElts;
    ListCell *lc = NULL;
    StringInfoData sid;
    int colcnt = 0;

    initStringInfo(&sid);

    lc = list_head(coldefs);

    for (; lc; lc = lnext(lc)) /* for all cols */
    {
      Node *pCol = lfirst(lc);
      ColumnDef *pColDef;

      if (nodeTag(pCol) != T_ColumnDef) /* avoid constraints, etc */
        continue;

      pColDef = (ColumnDef *)pCol;

      if (colcnt) {
        appendStringInfo(&sid, ", ");
      }

      appendStringInfo(&sid, "new.%s", pColDef->colname);

      colcnt++;
    } /* end for all cols */

    newVals[0] = '\0';
    snprintf(newVals, sizeof(newVals), "VALUES (%s)", sid.data);
    pfree(sid.data);
  }

  parent_tab_name = makeNode(RangeVar);
  parent_tab_name->catalogname = cxt->relation->catalogname;
  parent_tab_name->schemaname = cxt->relation->schemaname;
  parent_tab_name->relname = cxt->relation->relname;
  parent_tab_name->location = -1;

  child_tab_name = makeNode(RangeVar);
  child_tab_name->catalogname = cxt->relation->catalogname;
  child_tab_name->schemaname = cxt->relation->schemaname;
  child_tab_name->relname = child_name_str;
  child_tab_name->location = -1;

  snprintf(
      ruleBuf, sizeof(ruleBuf),
      "CREATE RULE %s AS ON INSERT to %s WHERE %s DO INSTEAD INSERT INTO %s %s",
      child_name_str, parent_tab_name->relname, exprBuf, child_name_str,
      newVals);

  pIns = makeNode(InsertStmt);

  pResult = (Node *)pIns;
  pIns->relation = makeNode(RangeVar);
  pIns->relation->catalogname = NULL;
  pIns->relation->schemaname = NULL;
  pIns->relation->relname = "partition_rule";
  pIns->relation->location = -1;
  pIns->returningList = NULL;

  pIns->cols = NIL;

  if (1) {
    List *vl1 = NULL;

    A_Const *acs = makeNode(A_Const);
    acs->val.type = T_String;
    acs->val.val.str = pstrdup(parent_tab_name->relname);
    acs->typname = SystemTypeName("text");
    acs->location = -1;

    vl1 = list_make1(acs);

    acs = makeNode(A_Const);
    acs->val.type = T_String;
    acs->val.val.str = pstrdup(child_name_str);
    acs->typname = SystemTypeName("text");
    acs->location = -1;

    vl1 = lappend(vl1, acs);

    acs = makeNode(A_Const);
    acs->val.type = T_String;
    acs->val.val.str = pstrdup(exprBuf);
    acs->typname = SystemTypeName("text");
    acs->location = -1;

    vl1 = lappend(vl1, acs);

    acs = makeNode(A_Const);
    acs->val.type = T_String;
    acs->val.val.str = pstrdup(ruleBuf);
    acs->typname = SystemTypeName("text");
    acs->location = -1;

    vl1 = lappend(vl1, acs);

    pIns->selectStmt = (Node *)makeNode(SelectStmt);
    ((SelectStmt *)pIns->selectStmt)->valuesLists = list_make1(vl1);
  }

  return (pResult);
} /* end make_prule_catalog */

static Node *make_prule_rulestmt(ParseState *pstate, CreateStmtContext *cxt,
                                 CreateStmtBase *stmt, Node *partitionBy,
                                 PartitionElem *pElem, char *at_depth,
                                 char *child_name_str, char *exprBuf,
                                 Node *pWhere) {
  Node *pResult = NULL;
  RuleStmt *pRule = NULL;
  InsertStmt *pIns = NULL;
  RangeVar *parent_tab_name;
  RangeVar *child_tab_name;

  parent_tab_name = makeNode(RangeVar);
  parent_tab_name->catalogname = cxt->relation->catalogname;
  parent_tab_name->schemaname = cxt->relation->schemaname;
  parent_tab_name->relname = cxt->relation->relname;
  parent_tab_name->location = -1;

  child_tab_name = makeNode(RangeVar);
  child_tab_name->catalogname = cxt->relation->catalogname;
  child_tab_name->schemaname = cxt->relation->schemaname;
  child_tab_name->relname = child_name_str;
  child_tab_name->location = -1;

  pIns = makeNode(InsertStmt);

  pRule = makeNode(RuleStmt);
  pRule->replace = false; /* do not replace */
  pRule->relation = parent_tab_name;
  pRule->rulename = pstrdup(child_name_str);
  pRule->whereClause = pWhere;
  pRule->event = CMD_INSERT;
  pRule->instead = true; /* do instead */
  pRule->actions = list_make1(pIns);

  pResult = (Node *)pRule;

  pIns->relation = makeNode(RangeVar);
  pIns->relation->catalogname = cxt->relation->catalogname;
  pIns->relation->schemaname = cxt->relation->schemaname;
  pIns->relation->relname = child_name_str;
  pIns->relation->location = -1;

  pIns->returningList = NULL;

  pIns->cols = NIL;

  if (1) {
    List *coldefs = stmt->tableElts;
    ListCell *lc = NULL;
    List *vl1 = NULL;

    lc = list_head(coldefs);

    for (; lc; lc = lnext(lc)) /* for all cols */
    {
      Node *pCol = lfirst(lc);
      ColumnDef *pColDef;
      ColumnRef *pCRef;

      if (nodeTag(pCol) != T_ColumnDef) /* avoid constraints, etc */
        continue;

      pCRef = makeNode(ColumnRef);

      pColDef = (ColumnDef *)pCol;

      pCRef->location = -1;
      /* NOTE: gram.y uses "*NEW*" for "new" */
      pCRef->fields =
          list_make2(makeString("*NEW*"), makeString(pColDef->colname));

      vl1 = lappend(vl1, pCRef);
    }

    pIns->selectStmt = (Node *)makeNode(SelectStmt);
    ((SelectStmt *)pIns->selectStmt)->valuesLists = list_make1(vl1);
  }

  return (pResult);
} /* end make_prule_rulestmt */

List *make_partition_rules(ParseState *pstate, CreateStmtContext *cxt,
                           CreateStmtBase *stmt, Node *partitionBy,
                           PartitionElem *pElem, char *at_depth,
                           char *child_name_str, int partNumId, int maxPartNum,
                           int everyOffset, int maxEveryOffset,
                           ListCell **pp_lc_anp, bool doRuleStmt) {
  PartitionBy *pBy = (PartitionBy *)partitionBy;
  Node *pRule = NULL;
  List *allRules = NULL;

  if (pBy->partType != PARTTYP_HASH) {
    Assert(pElem);
  }
  if (pBy->partType == PARTTYP_HASH) {
    List *colElts = pBy->keys;
    ListCell *lc = NULL;
    char exprBuf[10000];
    StringInfoData sid;
    int colcnt = 0;
    Node *pHashArgs = NULL;
    Node *pExpr = NULL;
    PartitionBoundSpec *pBSpec = NULL;

    if (pElem) {
      pBSpec = (PartitionBoundSpec *)pElem->boundSpec;
    }
    exprBuf[0] = '\0';

    initStringInfo(&sid);

    lc = list_head(colElts);

    for (; lc; lc = lnext(lc)) /* for all cols */
    {
      Node *pCol = lfirst(lc);
      Value *pColConst;
      ColumnRef *pCRef = NULL;

      pColConst = (Value *)pCol;

      Assert(IsA(pColConst, String));

      if (!deparse_partition_rule((Node *)pCol, exprBuf, sizeof(exprBuf))) {
        int loco = pBy->location;

        if (pBSpec) loco = pBSpec->location;

        ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                        errmsg("unknown type or operator%s", at_depth),
                        parser_errposition(pstate, loco)));
      }

      pCRef = makeNode(ColumnRef);
      pCRef->location = -1;
      pCRef->fields = list_make1(pCol);

      if (colcnt) {
        Node *pAppOp = NULL;

        pAppOp = (Node *)makeSimpleA_Expr(AEXPR_OP, "||", pHashArgs,
                                          (Node *)pCRef, -1);
        pHashArgs = pAppOp;

        appendStringInfo(&sid, "||");
      } else {
        pHashArgs = (Node *)pCRef;
      }

      appendStringInfo(&sid, "%s", exprBuf);

      colcnt++;
    } /* end for all cols */

    /* magic_hash: maximum number of partitions is maxPartNum
       current partition number is parNumId
    */

    /* modulus arithmetic is 0 to N-1, so go to (partNumId-1).
     * Also, hashtext can return a negative, so fix it up */

    snprintf(exprBuf, sizeof(exprBuf),
             /*				 "magic_hash(%d, %s) = %d", */
             /* double % for % literal - ((hash(cols)%max + max) % max) */
             "(hashtext(%s)%%%d + %d)%%%d = %d", sid.data, maxPartNum,
             maxPartNum, maxPartNum, (partNumId - 1));

    pfree(sid.data);

    {
      Node *pPlusOp = NULL;
      Node *pModOp = NULL;
      A_Const *pModBy = NULL;
      A_Const *pPartID = NULL;
      FuncCall *pFC = makeNode(FuncCall);

      pFC->funcname = list_make1(makeString("hashtext"));
      pFC->args = list_make1(pHashArgs);
      pFC->agg_star = FALSE;
      pFC->agg_distinct = FALSE;
      pFC->location = -1;
      pFC->over = NULL;

      pModBy = makeNode(A_Const);
      pModBy->val.type = T_Integer;
      pModBy->val.val.ival = maxPartNum;
      pModBy->location = -1;

      pPartID = makeNode(A_Const);
      pPartID->val.type = T_Integer;
      pPartID->val.val.ival = (partNumId - 1);
      pPartID->location = -1;

      pModOp = (Node *)makeSimpleA_Expr(AEXPR_OP, "%", (Node *)pFC,
                                        (Node *)pModBy, -1);

      pPlusOp = (Node *)makeSimpleA_Expr(AEXPR_OP, "+", (Node *)pModOp,
                                         (Node *)pModBy, -1);

      pModOp = (Node *)makeSimpleA_Expr(AEXPR_OP, "%", (Node *)pPlusOp,
                                        (Node *)pModBy, -1);

      pExpr =
          (Node *)makeSimpleA_Expr(AEXPR_OP, "=", pModOp, (Node *)pPartID, -1);
    }

    /* first the CHECK constraint, then the INSERT statement, then
     * the RULE statement
     */
    allRules = list_make1(pExpr);

    if (doRuleStmt) {
      pRule = make_prule_catalog(pstate, cxt, stmt, partitionBy, pElem,
                                 at_depth, child_name_str, exprBuf, pExpr);

      allRules = lappend(allRules, pRule);

      pRule = make_prule_rulestmt(pstate, cxt, stmt, partitionBy, pElem,
                                  at_depth, child_name_str, exprBuf, pExpr);

      allRules = lappend(allRules, pRule);
    }

  } /* end if HASH */

  if (pBy->partType == PARTTYP_LIST) {
    Node *pIndAND = NULL;
    Node *pIndOR = NULL;
    List *colElts = pBy->keys;
    ListCell *lc = NULL;
    List *valElts = NULL;
    ListCell *lc_val = NULL;
    ListCell *lc_valent = NULL;
    char exprBuf[10000];
    char ANDBuf[10000];
    char ORBuf[10000];
    PartitionValuesSpec *spec = (PartitionValuesSpec *)pElem->boundSpec;

    exprBuf[0] = '\0';
    ANDBuf[0] = '\0';
    ORBuf[0] = '\0';

    valElts = spec->partValues;
    lc_valent = list_head(valElts);

    /* number of values is a multiple of the number of columns */
    if (lc_valent) lc_val = list_head((List *)lfirst(lc_valent));
    for (; lc_val;) /* for all vals */
    {
      lc = list_head(colElts);
      pIndAND = NULL;

      for (; lc; lc = lnext(lc)) /* for all cols */
      {
        Node *pCol = lfirst(lc);
        Node *pEq = NULL;
        Value *pColConst;
        A_Const *pValConst;
        ColumnRef *pCRef;
        bool isnull = false;

        pCRef = makeNode(ColumnRef); /* need columnref for WHERE */

        if (NULL == lc_val) {
          ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                          errmsg("mismatched columns for VALUES%s", at_depth),
                          parser_errposition(pstate, spec->location)));
        }

        pColConst = (Value *)pCol;
        pValConst = (A_Const *)lfirst(lc_val);

        Assert(IsA(pColConst, String));

        pCRef->location = -1;
        pCRef->fields = list_make1(pColConst);

        if (!(IsA(pValConst, A_Const))) {
          Const *c = (Const *)pValConst;
          Type typ;
          Form_pg_type pgtype;
          Datum dat;
          A_Const *aconst;
          Value *val;

          Assert(IsA(c, Const));

          if (c->constisnull) {
            isnull = true;
            aconst = NULL;
          } else {
            aconst = makeNode(A_Const);
            typ = typeidType(c->consttype);
            pgtype = (Form_pg_type)GETSTRUCT(typ);
            dat = OidFunctionCall1(pgtype->typoutput, c->constvalue);

            ReleaseType(typ);
            val = makeString(DatumGetCString(dat));
            aconst->val = *val;
            aconst->location = -1;
          }

          pValConst = aconst;
        }

        if (isnull) {
          NullTest *nt = makeNode(NullTest);
          nt->arg = (Expr *)pCRef;
          nt->nulltesttype = IS_NULL;
          pEq = (Node *)nt;
        } else
          /* equality expression: column = value */
          pEq = (Node *)makeSimpleA_Expr(AEXPR_OP, "=", (Node *)pCRef,
                                         (Node *)pValConst, -1 /* position */);

        if (!deparse_partition_rule((Node *)pEq, exprBuf, sizeof(exprBuf))) {
          ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                          errmsg("unknown type or operator%s", at_depth),
                          parser_errposition(pstate, spec->location)));
        }

        /* for multiple cols - AND the matches eg:
           (col = value) AND (col = value)
        */
        if (pIndAND) {
          char *pfoo = pstrdup(ANDBuf);

          pIndAND = (Node *)makeA_Expr(AEXPR_AND, NIL, pIndAND, pEq,
                                       -1 /* position */);

          snprintf(ANDBuf, sizeof(ANDBuf), "((%s) and %s)", exprBuf, pfoo);

          pfree(pfoo);
        } else {
          pIndAND = pEq;
          snprintf(ANDBuf, sizeof(ANDBuf), "(%s)", exprBuf);
        }

        lc_val = lnext(lc_val);
      } /* end for all cols */

      /* if more VALUES than columns, then multiple matching
         conditions, so OR them eg:

         ((col = value) AND (col = value)) OR
         ((col = value) AND (col = value)) OR
         ((col = value) AND (col = value))

      */

      if (pIndOR) {
        char *pfoo = pstrdup(ORBuf);

        pIndOR = (Node *)makeA_Expr(AEXPR_OR, NIL, pIndOR, pIndAND,
                                    -1 /* position */);

        snprintf(ORBuf, sizeof(ORBuf), "((%s) OR %s)", ANDBuf, pfoo);

        pfree(pfoo);

      } else {
        pIndOR = pIndAND;
        snprintf(ORBuf, sizeof(ORBuf), "(%s)", ANDBuf);
      }
      if (lc_val == NULL) {
        lc_valent = lnext(lc_valent);
        if (lc_valent) lc_val = list_head((List *)lfirst(lc_valent));
      }

    } /* end for all vals */

    /* first the CHECK constraint, then the INSERT statement, then
     * the RULE statement
     */
    allRules = list_make1(pIndOR);

    if (doRuleStmt) {
      pRule = make_prule_catalog(pstate, cxt, stmt, partitionBy, pElem,
                                 at_depth, child_name_str, ORBuf, pIndOR);

      allRules = lappend(allRules, pRule);

      pRule = make_prule_rulestmt(pstate, cxt, stmt, partitionBy, pElem,
                                  at_depth, child_name_str, ORBuf, pIndOR);

      allRules = lappend(allRules, pRule);
    }
  } /* end if LIST */

  /*
       For RANGE partitions with an EVERY clause, the following
           fields are defined:
           int everyOffset - 0 for no EVERY, else 1 to maxEveryOffset
           int maxEveryOffset - 1 for no EVERY, else >1
           ListCell	**pp_lc_anp - the pointer to the pointer
                                    to the ListCell of [A]ll[N]ew[P]artitions,
                                                            a list of lists of
     stringified values
                                                            (as opposed to
     A_Const's).
  */

  if (pBy->partType == PARTTYP_RANGE) {
    Node *pIndAND = NULL;
    Node *pIndOR = NULL;
    List *colElts = pBy->keys;
    ListCell *lc = NULL;
    List *valElts = NULL;
    ListCell *lc_val = NULL;
    PartitionRangeItem *pRI = NULL;
    char exprBuf[10000];
    char ANDBuf[10000];
    char ORBuf[10000];
    int range_idx;
    PartitionBoundSpec *pBSpec = NULL;

    ListCell *lc_every_val = NULL;
    List *allNewCols = NIL;

    if (pElem) {
      pBSpec = (PartitionBoundSpec *)pElem->boundSpec;
    }
    exprBuf[0] = '\0';
    ANDBuf[0] = '\0';
    ORBuf[0] = '\0';

    pRI = (PartitionRangeItem *)(pBSpec->partStart);

    if (pRI)
      valElts = pRI->partRangeVal;
    else
      valElts = NULL;

    if (maxEveryOffset > 1) /* if have an EVERY clause */
    {
      ListCell *lc_anp = NULL;

      /* check the list of "all new partitions" */
      if (pp_lc_anp) {
        lc_anp = *pp_lc_anp;
        if (lc_anp) {
          allNewCols = lfirst(lc_anp);

          /* find the list of columns for a new partition */
          if (allNewCols) {
            lc_every_val = list_head(allNewCols);
          }
        }
      }
    } /* end if every */

    /* number of values must equal of the number of columns */

    for (range_idx = 0; range_idx < 2; range_idx++) {
      char *expr_op = ">";

      if (everyOffset > 1) /* for generated START for EVERY */
        expr_op = ">=";    /* always be inclusive           */
      else
          /* only inclusive set that way */
          if (pRI && (PART_EDGE_INCLUSIVE == pRI->partedge))
        expr_op = ">=";

      if (range_idx) /* Only do it for the upper bound iteration */
      {
        pRI = (PartitionRangeItem *)(pBSpec->partEnd);

        if (pRI)
          valElts = pRI->partRangeVal;
        else
          valElts = NULL;

        expr_op = "<";

        /* for generated END for EVERY always be exclusive */
        if ((everyOffset + 1) < maxEveryOffset)
          expr_op = "<";
        else
            /* only be inclusive if set that way */
            if (pRI && (PART_EDGE_INCLUSIVE == pRI->partedge))
          expr_op = "<=";

        /* If have EVERY, and not the very first START or last END */
        if ((0 != everyOffset) && (everyOffset + 1 <= maxEveryOffset)) {
          if (*pp_lc_anp) {
            if (everyOffset != 1) *pp_lc_anp = lnext(*pp_lc_anp);

            if (*pp_lc_anp) {
              allNewCols = lfirst(*pp_lc_anp);

              if (allNewCols) lc_every_val = list_head(allNewCols);
            }
          }
        }
      } /* end if range_idx != 0 */

      lc_val = list_head(valElts);

      for (; lc_val;) /* for all vals */
      {
        lc = list_head(colElts);
        pIndAND = NULL;

        for (; lc; lc = lnext(lc)) /* for all cols */
        {
          Node *pCol = lfirst(lc);
          Node *pEq = NULL;
          Value *pColConst;
          A_Const *pValConst;
          ColumnRef *pCRef;

          pCRef = makeNode(ColumnRef); /* need columnref for WHERE */

          if (NULL == lc_val) {
            char *st_end = "START";

            if (range_idx) {
              st_end = "END";
            }

            ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                            errmsg("too few columns in %s specification%s",
                                   st_end, at_depth),
                            parser_errposition(pstate, pBSpec->location)));
          }

          pColConst = (Value *)pCol;
          pValConst = (A_Const *)lfirst(lc_val);

          Assert(IsA(pColConst, String));

          pCRef->location = -1;
          pCRef->fields = list_make1(pColConst);

          if (!(IsA(pValConst, A_Const))) {
            Const *c = (Const *)pValConst;
            Type typ;
            Form_pg_type pgtype;
            Datum dat;
            A_Const *aconst = makeNode(A_Const);
            Value *val;

            Assert(IsA(c, Const));

            typ = typeidType(c->consttype);
            pgtype = (Form_pg_type)GETSTRUCT(typ);
            dat = OidFunctionCall1(pgtype->typoutput, c->constvalue);

            val = makeString(DatumGetCString(dat));
            aconst->val = *val;
            aconst->location = -1;
            ReleaseType(typ);

            pValConst = aconst;
          }

          pEq = (Node *)makeSimpleA_Expr(AEXPR_OP, expr_op, (Node *)pCRef,
                                         (Node *)pValConst, -1 /* position */);

          if (!deparse_partition_rule((Node *)pEq, exprBuf, sizeof(exprBuf))) {
            ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                            errmsg("unknown type or operator%s", at_depth),
                            parser_errposition(pstate, pBSpec->location)));
          }

          /* for multiple cols - AND the matches eg:
             (col = value) AND (col = value)
          */
          if (pIndAND) {
            char *pfoo = pstrdup(ANDBuf);

            pIndAND = (Node *)makeA_Expr(AEXPR_AND, NIL, pIndAND, pEq,
                                         -1 /* position */);

            snprintf(ANDBuf, sizeof(ANDBuf), "((%s) and %s)", exprBuf, pfoo);

            pfree(pfoo);
          } else {
            pIndAND = pEq;
            snprintf(ANDBuf, sizeof(ANDBuf), "(%s)", exprBuf);
          }

          lc_val = lnext(lc_val);

          if (((0 == range_idx) && (everyOffset > 1)) ||
              ((1 == range_idx) && (everyOffset + 1 < maxEveryOffset))) {
            if (lc_every_val) {
              lc_every_val = lnext(lc_every_val);
            }
          }
        } /* end for all cols */

        /* if more VALUES than columns, then complain
        */
        if (lc_val) {
          char *st_end = "START";

          if (range_idx) {
            st_end = "END";
          }

          ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                          errmsg("too many columns in %s specification%s",
                                 st_end, at_depth),
                          parser_errposition(pstate, pBSpec->location)));
        }

        if (pIndOR) {
          char *pfoo = pstrdup(ORBuf);
          /* XXX XXX build an AND for now.  But later we
           * split this to distinguish START and END
           * conditions */

          pIndOR = (Node *)makeA_Expr(AEXPR_AND, NIL, pIndOR, pIndAND,
                                      -1 /* position */);

          snprintf(ORBuf, sizeof(ORBuf), "((%s) AND %s)", ANDBuf, pfoo);

          pfree(pfoo);

        } else {
          pIndOR = pIndAND;
          snprintf(ORBuf, sizeof(ORBuf), "(%s)", ANDBuf);
        }

      } /* end for all vals */

    } /* end for range_idx */

    /* first the CHECK constraint, then the INSERT statement, then
     * the RULE statement
     */
    allRules = list_make1(pIndOR);

    if (doRuleStmt) {
      pRule = make_prule_catalog(pstate, cxt, stmt, partitionBy, pElem,
                                 at_depth, child_name_str, ORBuf, pIndOR);
      allRules = lappend(allRules, pRule);

      pRule = make_prule_rulestmt(pstate, cxt, stmt, partitionBy, pElem,
                                  at_depth, child_name_str, ORBuf, pIndOR);
      allRules = lappend(allRules, pRule);
    }
  } /* end if RANGE */

  return allRules;
} /* end make_partition_rules */

/* XXX: major cleanup required. Get rid of gotos at least */
static int partition_range_compare(ParseState *pstate, CreateStmtContext *cxt,
                                   CreateStmtBase *stmt, PartitionBy *pBy,
                                   char *at_depth, int partNumber,
                                   char *compare_op, /* =, <, > only */
                                   PartitionRangeItem *pRI1,
                                   PartitionRangeItem *pRI2) {
  int rc = -1;
  ListCell *lc1 = NULL;
  ListCell *lc2 = NULL;
  List *cop = lappend(NIL, makeString(compare_op));

  if (!pRI1 || !pRI2) /* error */
    return rc;

  lc1 = list_head(pRI1->partRangeVal);
  lc2 = list_head(pRI2->partRangeVal);
L_redoLoop:
  for (; lc1 && lc2;) {
    Node *n1 = lfirst(lc1);
    Node *n2 = lfirst(lc2);

    if (!equal(n1, n2)) break;

    lc1 = lnext(lc1);
    lc2 = lnext(lc2);
  }

  if (!lc1 && !lc2) /* both empty, so all values are equal */
  {
    if (strcmp("=", compare_op) == 0)
      return 1;
    else
      return 0;
  } else if (lc1 && lc2) /* both not empty, so last value was different */
  {
    Datum res;
    Node *n1 = lfirst(lc1);
    Node *n2 = lfirst(lc2);

    /* XXX XXX: can't trust equal() code on "typed" data like date
     * types (because it compares things like "location") so do a
     * real compare using eval .
     */
    res = eval_basic_opexpr(pstate, cop, n1, n2, NULL, NULL, NULL, -1);

    if (DatumGetBool(res) && strcmp("=", compare_op) == 0) {
      /* surprise! they were equal after all.  So keep going... */
      lc1 = lnext(lc1);
      lc2 = lnext(lc2);

      goto L_redoLoop;
    }

    return DatumGetBool(res);
  } else if (lc1 || lc2) {
    /* lists of different lengths */
    if (strcmp("=", compare_op) == 0) return 0;

    /* longer list is bigger? */

    if (strcmp("<", compare_op) == 0) {
      if (lc1)
        return 0;
      else
        return 1;
    }
    if (strcmp(">", compare_op) == 0) {
      if (lc1)
        return 1;
      else
        return 0;
    }
  }

  return rc;
} /* end partition_range_compare */

static int part_el_cmp(void *a, void *b, void *arg) {
  RegProcedure **sortfuncs = (RegProcedure **)arg;
  PartitionElem *el1 = (PartitionElem *)a;
  PartitionElem *el2 = (PartitionElem *)b;
  PartitionBoundSpec *bs1;
  PartitionBoundSpec *bs2;
  int i;
  List *start1 = NIL, *start2 = NIL;
  ListCell *lc1, *lc2;

  /*
   * We call this function from all over the place so don't assume that
   * things are valid.
   */

  if (!el1)
    return -1;
  else if (!el2)
    return 1;

  if (el1->isDefault) return -1;

  if (el2->isDefault) return 1;

  bs1 = (PartitionBoundSpec *)el1->boundSpec;
  bs2 = (PartitionBoundSpec *)el2->boundSpec;

  if (!bs1) return -1;
  if (!bs2) return 1;

  if (bs1->partStart)
    start1 = ((PartitionRangeItem *)bs1->partStart)->partRangeVal;
  if (bs2->partStart)
    start2 = ((PartitionRangeItem *)bs2->partStart)->partRangeVal;

  if (!start1 && !start2) {
    /* these cases are syntax errors but they'll be picked up later */
    if (!bs1->partEnd && !bs2->partEnd)
      return 0;
    else if (!bs1->partEnd)
      return 1;
    else if (!bs2->partEnd)
      return -1;
    else {
      List *end1 = ((PartitionRangeItem *)bs1->partEnd)->partRangeVal;
      List *end2 = ((PartitionRangeItem *)bs2->partEnd)->partRangeVal;

      i = 0;
      forboth(lc1, end1, lc2, end2) {
        Const *c1 = lfirst(lc1);
        Const *c2 = lfirst(lc2);

        /* use < */
        RegProcedure sortFunction = sortfuncs[0][i++];

        if (DatumGetBool(
                OidFunctionCall2(sortFunction, c1->constvalue, c2->constvalue)))
          return -1; /* a < b */
        if (DatumGetBool(
                OidFunctionCall2(sortFunction, c2->constvalue, c1->constvalue)))
          return 1;
      }
      /* equal */
      return 0;
    }
  } else if (!start1 && start2) {
    /*
     * compare end1 to start2. Whether the end and start are inclusive
     * or exclusive is very important here. For example, if
     * end1 is exclusive and start2 is inclusive, their being equal means
     * that end1 finishes *before* start2.
     */

    PartitionRangeItem *pri1 = (PartitionRangeItem *)bs1->partEnd;
    PartitionRangeItem *pri2 = (PartitionRangeItem *)bs2->partStart;
    List *end1 = pri1->partRangeVal;
    PartitionEdgeBounding pe1 = pri1->partedge;
    PartitionEdgeBounding pe2 = pri2->partedge;

    Assert(pe1 != PART_EDGE_UNSPECIFIED);
    Assert(pe2 != PART_EDGE_UNSPECIFIED);

    i = 0;
    forboth(lc1, end1, lc2, start2) {
      Const *c1 = lfirst(lc1);
      Const *c2 = lfirst(lc2);
      RegProcedure sortFunction;

      sortFunction = sortfuncs[0][i];

      /* try < first */
      if (DatumGetBool(
              OidFunctionCall2(sortFunction, c1->constvalue, c2->constvalue)))
        return -1;

      /* see if they're equal */
      sortFunction = sortfuncs[1][i];

      if (DatumGetBool(
              OidFunctionCall2(sortFunction, c1->constvalue, c2->constvalue))) {
        /* equal, but that might actually mean < */
        if (pe1 == PART_EDGE_EXCLUSIVE)
          return -1; /* it's less than */
        else if (pe1 == PART_EDGE_INCLUSIVE && pe2 == PART_EDGE_EXCLUSIVE)
          return -1; /* it's less than */

        /* otherwise, they're equal */
      } else
        return 1;

      i++;
    }
    return 0;
  } else if (start1 && !start2) {
    /* opposite of above */
    return -part_el_cmp(b, a, arg);
  } else {
    /* we have both starts */
    PartitionRangeItem *pri1 = (PartitionRangeItem *)bs1->partStart;
    PartitionRangeItem *pri2 = (PartitionRangeItem *)bs2->partStart;
    PartitionEdgeBounding pe1 = pri1->partedge;
    PartitionEdgeBounding pe2 = pri2->partedge;
    i = 0;
    forboth(lc1, start1, lc2, start2) {
      Const *c1 = lfirst(lc1);
      Const *c2 = lfirst(lc2);

      /* use < */
      RegProcedure sortFunction = sortfuncs[0][i];

      if (DatumGetBool(
              OidFunctionCall2(sortFunction, c1->constvalue, c2->constvalue)))
        return -1; /* a < b */

      sortFunction = sortfuncs[1][i];
      if (DatumGetBool(
              OidFunctionCall2(sortFunction, c1->constvalue, c2->constvalue))) {
        if (pe1 == PART_EDGE_INCLUSIVE && pe2 == PART_EDGE_EXCLUSIVE)
          return -1; /* actually, it was < */
        else if (pe1 == PART_EDGE_EXCLUSIVE && pe2 == PART_EDGE_INCLUSIVE)
          return 1;

      } else
        return 1;

      i++;
    }
  }

  /* all equal */
  return 0;
}

static List *sort_range_elems(List *opclasses, List *elems) {
  ListCell *lc;
  PartitionElem *clauses;
  RegProcedure *sortfuncs[2];
  int i;
  List *newelems = NIL;

  sortfuncs[0] = palloc(list_length(opclasses) * sizeof(RegProcedure));
  sortfuncs[1] = palloc(list_length(opclasses) * sizeof(RegProcedure));
  i = 0;

  foreach (lc, opclasses) {
    Oid opclass = lfirst_oid(lc);
    Oid opoid = get_opclass_member(opclass, InvalidOid, BTLessStrategyNumber);

    /* < first */
    sortfuncs[0][i] = get_opcode(opoid);

    opoid = get_opclass_member(opclass, InvalidOid, BTEqualStrategyNumber);

    sortfuncs[1][i] = get_opcode(opoid);
    i++;
  }

  i = 0;
  clauses = palloc(sizeof(PartitionElem) * list_length(elems));

  foreach (lc, elems)
    clauses[i++] = *(PartitionElem *)lfirst(lc);

  qsort_arg(clauses, list_length(elems), sizeof(PartitionElem),
            (qsort_arg_comparator)part_el_cmp, (void *)sortfuncs);

  for (i = 0; i < list_length(elems); i++)
    newelems = lappend(newelems, &clauses[i]);

  return newelems;
}

static void preprocess_range_spec(partValidationState *vstate) {
  PartitionSpec *spec = (PartitionSpec *)vstate->pBy->partSpec;
  ParseState *pstate = vstate->pstate;
  ListCell *lc;
  List *plusop = list_make2(makeString("pg_catalog"), makeString("+"));
  List *ltop = list_make2(makeString("pg_catalog"), makeString("<"));
  List *coltypes = NIL;
  List *stenc;
  List *newelts = NIL;

  foreach (lc, vstate->pBy->keys) {
    char *colname = strVal(lfirst(lc));
    bool found = false;
    ListCell *lc2;
    TypeName *typname = NULL;

    foreach (lc2, vstate->cxt->columns) {
      ColumnDef *column = lfirst(lc2);

      if (strcmp(column->colname, colname) == 0) {
        found = true;
        if (!OidIsValid(column->typname->typid)) {
          Type type = typenameType(vstate->pstate, column->typname);
          column->typname->typid = typeTypeId(type);
          ReleaseType(type);
        }
        typname = column->typname;
        break;
      }
    }
    Assert(found);
    coltypes = lappend(coltypes, typname);
  }

  partition_range_every(pstate, vstate->pBy, coltypes, vstate->at_depth,
                        vstate);

  /*
   * Must happen after partition_range_every(), since that gathers up
   * encoding clauses for us.
   */
  stenc = spec->enc_clauses;

  /*
   * Iterate over the elements in a given partition specification and
   * expand any EVERY clauses into raw elements.
   */
  foreach (lc, spec->partElem) {
    PartitionElem *el = lfirst(lc);
    PartitionBoundSpec *pbs = (PartitionBoundSpec *)el->boundSpec;

    Node *pStoreAttr = NULL;
    ListCell *lc2;
    bool bTablename = false;

    if (IsA(el, ColumnReferenceStorageDirective)) {
      stenc = lappend(stenc, el);
      continue;
    }

    /* we might not have a boundary spec if user just specified DEFAULT */
    if (!pbs) {
      newelts = lappend(newelts, el);
      continue;
    }

    pbs = (PartitionBoundSpec *)transformExpr(pstate, (Node *)pbs);

    pStoreAttr = el->storeAttr;

    /* MPP-6297: check for WITH (tablename=name) clause
     * [only for dump/restore, set deep in the guts of
     * partition_range_every...]
     */
    bTablename = (NULL != pbs->pWithTnameStr);

    if (!bTablename && pbs->partEvery) {
      /* the start expression might come from a previous end
       * expression
       */
      List *start = NIL;
      List *curstart = NIL;
      List *end = ((PartitionRangeItem *)pbs->partEnd)->partRangeVal;
      List *newend = NIL;
      List *every =
          (List *)((PartitionRangeItem *)pbs->partEvery)->partRangeVal;
      List *everyinc;
      bool lessthan = true;
      int everycount = 0;
      List *everyelts = NIL;
      bool first = true;
      List *everytypes = NIL;
      int i;

      Assert(pbs->partStart);
      Assert(pbs->partEnd);
      Assert(IsA(end, List));
      Assert(IsA(every, List));

      /* we modify every, so copy it */
      everyinc = copyObject(every);

      while (lessthan) {
        Datum res;
        Oid restypid;
        PartitionElem *newel;
        PartitionBoundSpec *newbs;
        PartitionRangeItem *newri;
        ListCell *lctypes, *lceveryinc;
        ListCell *lcstart, *lcend, *lcevery, *lccol, *lclastend;
        List *lastend = NIL;

        /*
         * Other parts of the parser want to know how many clauses
         * we expanded here.
         */
        everycount++;

        if (start == NIL) {
          start = ((PartitionRangeItem *)pbs->partStart)->partRangeVal;

          lcend = list_head(end);
          /* coerce to target type */
          forboth(lcstart, start, lccol, coltypes) {
            TypeName *typ = lfirst(lccol);
            Node *newnode;

            newnode = coerce_partition_value(lfirst(lcstart), typ->typid,
                                             typ->typmod, PARTTYP_RANGE);

            lfirst(lcstart) = newnode;

            /* be sure we coerce the end value */
            newnode = coerce_partition_value(lfirst(lcend), typ->typid,
                                             typ->typmod, PARTTYP_RANGE);
            lfirst(lcend) = newnode;
            lcend = lnext(lcend);
          }

          curstart = copyObject(start);
          forboth(lccol, coltypes, lcevery, every) {
            TypeName *typ = lfirst(lccol);
            HeapTuple optup;
            List *opname =
                list_make2(makeString("pg_catalog"), makeString("+"));
            Node *e = lfirst(lcevery);
            Oid rtypeId = exprType(e);
            Oid newrtypeId;

            /* first, make sure we can build up an operator */
            optup = oper(pstate, opname, typ->typid, rtypeId, true, -1);
            if (!HeapTupleIsValid(optup))
              ereport(
                  ERROR,
                  (errcode(ERRCODE_SYNTAX_ERROR),
                   errmsg(
                       "could not identify operator for partitioning operation "
                       "between type \"%s\" and type \"%s\"",
                       format_type_be(typ->typid), format_type_be(rtypeId)),
                   errhint(
                       "Add an explicit cast to the partitioning parameters")));

            newrtypeId = ((Form_pg_operator)GETSTRUCT(optup))->oprright;
            ReleaseOperator(optup);

            if (rtypeId != newrtypeId) {
              Type newetyp = typeidType(newrtypeId);
              int4 typmod = ((Form_pg_type)GETSTRUCT(newetyp))->typtypmod;

              ReleaseType(newetyp);

              /* we need to coerce */
              e = coerce_partition_value(e, newrtypeId, typmod, PARTTYP_RANGE);

              lfirst(lcevery) = e;
              rtypeId = newrtypeId;
            }

            everytypes = lappend_oid(everytypes, rtypeId);
          }
        } else {
          curstart = newend;
          lastend = newend;
          newend = NIL;
        }

        lcend = list_head(end);
        lcevery = list_head(everyinc);
        lclastend = list_head(lastend);

        forboth(lcstart, start, lccol, coltypes) {
          Const *mystart = lfirst(lcstart);
          TypeName *type = lfirst(lccol);
          Const *myend;
          Const *clauseend;
          Const *clauseevery;
          Oid typid = type->typid;
          int16 len = get_typlen(typid);
          bool typbyval = get_typbyval(typid);

          Assert(lcevery);
          Assert(lcend);

          clauseevery = lfirst(lcevery);
          clauseend = lfirst(lcend);

          /* add the every value to the start */
          res = eval_basic_opexpr(pstate, plusop, (Node *)mystart,
                                  (Node *)clauseevery, &typbyval, &len, &typid,
                                  -1);

          /* XXX: typmod ? */
          myend = makeConst(type->typid, type->typmod, len,
                            datumCopy(res, typbyval, len), false, typbyval);

          /* make sure res is bigger than the last value */
          if (lclastend) {
            Oid typ = InvalidOid;
            Const *prevval = (Const *)lfirst(lclastend);
            Datum is_lt =
                eval_basic_opexpr(pstate, ltop, (Node *)prevval, (Node *)myend,
                                  NULL, NULL, &typ, -1);

            if (!DatumGetBool(is_lt)) elog(ERROR, "every interval too small");
          }
          restypid = InvalidOid;
          res = eval_basic_opexpr(pstate, ltop, (Node *)myend,
                                  (Node *)clauseend, NULL, NULL, &restypid, -1);
          if (!DatumGetBool(res)) {
            newend = end;
            lessthan = false;
            break;
          }
          newend = lappend(newend, myend);

          lcend = lnext(lcend);
          lcevery = lnext(lcevery);
          if (lclastend) lclastend = lnext(lclastend);
        }

        lctypes = list_head(everytypes);
        forboth(lcevery, every, lceveryinc, everyinc) {
          Oid typid = lfirst_oid(lctypes);
          bool byval = get_typbyval(typid);
          int16 typlen = get_typlen(typid);
          Const *c;

          /* increment every */
          res = eval_basic_opexpr(pstate, plusop, (Node *)lfirst(lcevery),
                                  (Node *)lfirst(lceveryinc), NULL, NULL,
                                  &typid, -1);

          c = makeConst(typid, -1, typlen, res, false, byval);
          pfree(lfirst(lceveryinc));
          lfirst(lceveryinc) = c;
        }

        newel = makeNode(PartitionElem);
        newel->subSpec = copyObject(el->subSpec);
        newel->storeAttr = copyObject(el->storeAttr);
        newel->AddPartDesc = copyObject(el->AddPartDesc);
        newel->location = el->location;

        newbs = makeNode(PartitionBoundSpec);

        newel->boundSpec = (Node *)newbs;

        /* start */
        newri = makeNode(PartitionRangeItem);

        /* modifier only relevant on the first iteration */
        if (everycount == 1)
          newri->partedge = ((PartitionRangeItem *)pbs->partStart)->partedge;
        else
          newri->partedge = PART_EDGE_INCLUSIVE;

        newri->location = ((PartitionRangeItem *)pbs->partStart)->location;
        newri->partRangeVal = curstart;
        newbs->partStart = (Node *)newri;

        /* end */
        newri = makeNode(PartitionRangeItem);
        newri->partedge = PART_EDGE_EXCLUSIVE;
        newri->location = ((PartitionRangeItem *)pbs->partEnd)->location;
        newri->partRangeVal = newend;
        newbs->partEnd = (Node *)newri;

        /* every */
        newbs->partEvery = (Node *)copyObject(pbs->partEvery);
        newbs->location = pbs->location;
        newbs->everyGenList = pbs->everyGenList;

        everyelts = lappend(everyelts, newel);

        if (first) first = false;
      }

      /*
       * Update the final PartitionElem's partEnd modifier if it isn't
       * the default
       */
      if (((PartitionRangeItem *)pbs->partEnd)->partedge !=
          PART_EDGE_EXCLUSIVE) {
        PartitionElem *elem = lfirst(list_tail(everyelts));
        PartitionBoundSpec *s = (PartitionBoundSpec *)elem->boundSpec;
        PartitionRangeItem *ri = (PartitionRangeItem *)s->partEnd;

        ri->partedge = ((PartitionRangeItem *)pbs->partEnd)->partedge;
      }

      /* add everycount to each EVERY clause */
      i = 0;
      foreach (lc2, everyelts) {
        PartitionElem *el2 = lfirst(lc2);
        PartitionBoundSpec *pbs = (PartitionBoundSpec *)el2->boundSpec;
        PartitionRangeItem *ri = (PartitionRangeItem *)pbs->partEvery;

        ri->everycount = everycount;

        /*
         * Generate the new name, which is parname + every count
         * but only if every expands to more than one partition.
        */
        if (el->partName) {
          char newname[sizeof(NameData) + 10];

          if (list_length(everyelts) > 1)
            snprintf(newname, sizeof(newname), "%s_%u", strVal(el->partName),
                     ++i);
          else
            snprintf(newname, sizeof(newname), "%s", strVal(el->partName));

          if (strlen(newname) > NAMEDATALEN)
            ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                            errmsg("partition name \"%s\" too long",
                                   strVal(el->partName)),
                            parser_errposition(vstate->pstate, el->location)));

          el2->partName = (Node *)makeString(pstrdup(newname));
        }
      }

      newelts = list_concat(newelts, everyelts);
    } else {
      if (pbs->partStart) {
        ListCell *lccol;
        ListCell *lcstart;
        List *start = ((PartitionRangeItem *)pbs->partStart)->partRangeVal;

        /* coerce to target type */
        forboth(lcstart, start, lccol, coltypes) {
          Node *mystart = lfirst(lcstart);
          TypeName *typ = lfirst(lccol);
          Node *newnode;

          newnode = coerce_partition_value(mystart, typ->typid, typ->typmod,
                                           PARTTYP_RANGE);

          lfirst(lcstart) = newnode;
        }
      }

      if (pbs->partEnd) {
        List *end = ((PartitionRangeItem *)pbs->partEnd)->partRangeVal;
        ListCell *lccol;
        ListCell *lcend;

        /* coerce to target type */
        forboth(lcend, end, lccol, coltypes) {
          Node *myend = lfirst(lcend);
          TypeName *typ = lfirst(lccol);
          Node *newnode;

          newnode = coerce_partition_value(myend, typ->typid, typ->typmod,
                                           PARTTYP_RANGE);

          lfirst(lcend) = newnode;
        }
      }
      newelts = lappend(newelts, el);
    }
  }

  /* do an initial sort */
  spec->partElem = sort_range_elems(vstate->pBy->keyopclass, newelts);
  spec->enc_clauses = stenc;
}

static bool range_partition_walker(Node *node, void *context) {
  range_partition_ctx *ctx = (range_partition_ctx *)context;
  if (node == NULL)
    return false;
  else if (IsA(node, Const)) {
    Const *c = (Const *)node;

    if (c->constisnull)
      ereport(ERROR,
              (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
               errmsg("cannot use NULL with range partition specification"),
               errOmitLocation(true),
               parser_errposition(ctx->pstate, ctx->location)));
    return false;
  } else if (IsA(node, A_Const)) {
    A_Const *c = (A_Const *)node;
    if (IsA(&c->val, Null))
      ereport(ERROR,
              (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
               errmsg("cannot use NULL with range partition specification"),
               errOmitLocation(true),
               parser_errposition(ctx->pstate, ctx->location)));
    return false;
  }
  return expression_tree_walker(node, range_partition_walker, ctx);
}

void PartitionRangeItemIsValid(ParseState *pstate, PartitionRangeItem *pri) {
  ListCell *lc;
  range_partition_ctx ctx;

  if (!pri) return;

  ctx.pstate = pstate;
  ctx.location = pri->location;

  foreach (lc, pri->partRangeVal) { range_partition_walker(lfirst(lc), &ctx); }
}

/*
 * Basic partition validation:
 * Check that PARTITIONS matches specification (for HASH). Perform basic error
 * checking on boundary specifications.
 */
static void validate_range_partition(partValidationState *vstate) {
  bool bAppendRange = false;
  PartitionBoundSpec *prevBSpec = NULL;
  PartitionBoundSpec *spec = (PartitionBoundSpec *)vstate->spec;

  vstate->spec = (Node *)spec;

  if (spec) {
    /* XXX: create a type2name function */
    char *specTName = "LIST";

    if (IsA(spec, PartitionValuesSpec)) {
      ereport(ERROR,
              (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
               errmsg(
                   "invalid use of %s boundary "
                   "specification in partition clause",
                   specTName),
               /* MPP-4249: use value spec location if have one */
               errOmitLocation(true),
               parser_errposition(vstate->pstate,
                                  ((PartitionValuesSpec *)spec)->location)));
    }
  } else {
    ereport(ERROR,
            (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
             errmsg("missing boundary specification"), errOmitLocation(true),
             parser_errposition(vstate->pstate, vstate->pElem->location)));
  }

  {
    /* if the previous partition was named, and
     * current is not, and the prev and current
     * use single, complementary START/END specs,
     * then complain.
     */

    /* must have a valid RANGE pBSpec now or else
     * would have error'd out...
     */
    int currStartEnd = 0;

    if (spec->partStart) currStartEnd += 1;
    if (spec->partEnd) currStartEnd += 2;

    /* Note: for first loop, prevStartEnd = 0, so
     * this test is ok */

    if (((vstate->prevHadName && !(vstate->pElem->partName)) ||
         (!vstate->prevHadName && vstate->pElem->partName)) &&
        (((vstate->prevStartEnd == 1) && (currStartEnd == 2)) ||
         ((vstate->prevStartEnd == 2) && (currStartEnd == 1)))) {
      ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                      errmsg(
                          "invalid use of mixed named and "
                          "unnamed RANGE boundary "
                          "specifications%s",
                          vstate->at_depth),
                      errOmitLocation(true),
                      parser_errposition(vstate->pstate, spec->location)));
    }

    vstate->prevStartEnd = currStartEnd;
  }

  vstate->prevHadName = !!(vstate->pElem->partName); /* bool t/f */

  if (spec->partStart)
    PartitionRangeItemIsValid(vstate->pstate,
                              (PartitionRangeItem *)spec->partStart);
  if (spec->partEnd)
    PartitionRangeItemIsValid(vstate->pstate,
                              (PartitionRangeItem *)spec->partEnd);

  /*
   * Fixup boundaries for previous partition ending if necessary
   */
  if (!vstate->prevElem) {
    bAppendRange = true;
    goto L_setprevElem;
  }

  prevBSpec = (PartitionBoundSpec *)vstate->prevElem->boundSpec;
  /* XXX XXX: can check for overlap here too */

  if (!prevBSpec) {
    /* XXX XXX: if the previous partition declaration
     * does not have a boundary spec then its end
     * needs to be the start of the current */

    bAppendRange = true;
    goto L_setprevElem;
  }

  if (spec->partStart) {
    if (!prevBSpec->partEnd) {
      PartitionRangeItem *pRI = (PartitionRangeItem *)(spec->partStart);
      PartitionRangeItem *prevRI = makeNode(PartitionRangeItem);

      if (prevBSpec->partStart) {
        int compareRc = 0;
        PartitionRangeItem *pRI1 = (PartitionRangeItem *)spec->partStart;
        PartitionRangeItem *pRI2 = (PartitionRangeItem *)prevBSpec->partStart;

        compareRc = partition_range_compare(
            vstate->pstate, vstate->cxt, vstate->stmt, vstate->pBy,
            vstate->at_depth, vstate->partNumber, "<", pRI2, pRI1);

        if (1 != compareRc) {
          /* XXX: better message */
          ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                          errmsg(
                              "START of partition%s less "
                              "than START of previous%s",
                              vstate->namBuf, vstate->at_depth),
                          errOmitLocation(true),
                          parser_errposition(vstate->pstate, spec->location)));
        }
      }

      prevRI->location = pRI->location;

      prevRI->partRangeVal = list_copy(pRI->partRangeVal);

      /* invert sense of inclusiveness */
      prevRI->partedge = (PART_EDGE_INCLUSIVE == pRI->partedge)
                             ? PART_EDGE_EXCLUSIVE
                             : PART_EDGE_INCLUSIVE;

      prevBSpec->partEnd = (Node *)prevRI;

      /* don't need to check if range overlaps */
      bAppendRange = true;

    } else if (0) /* XXX XXX XXX XXX */
    {
      /* else if overlap complain */
      ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                      errmsg("start of partition%s overlaps previous%s",
                             vstate->namBuf, vstate->at_depth),
                      errOmitLocation(true),
                      parser_errposition(vstate->pstate, spec->location)));
    }

  }    /* end if current partStart */
  else /* no start, so use END of previous */
  {
    if (!prevBSpec->partEnd) {
      ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                      errmsg(
                          "cannot derive starting value of "
                          "partition%s based upon ending of "
                          "previous%s",
                          vstate->namBuf, vstate->at_depth),
                      errOmitLocation(true),
                      parser_errposition(vstate->pstate, spec->location)));

    } else /* build ri for current */
    {
      PartitionRangeItem *pRI = makeNode(PartitionRangeItem);
      PartitionRangeItem *prevRI = (PartitionRangeItem *)(prevBSpec->partEnd);

      pRI->location = prevRI->location;

      pRI->partRangeVal = list_copy(prevRI->partRangeVal);

      /* invert sense of inclusiveness */
      pRI->partedge = (PART_EDGE_INCLUSIVE == prevRI->partedge)
                          ? PART_EDGE_EXCLUSIVE
                          : PART_EDGE_INCLUSIVE;

      spec->partStart = (Node *)pRI;

      /* don't need to check if range overlaps */
      bAppendRange = true;
    } /* end build ri for current */
  }   /* end use END of previous */

L_setprevElem:

  /* check for overlap,
   * then add new partitions to sorted list
   */

  if (spec->partStart && spec->partEnd) {
    int compareRc = 0;
    PartitionRangeItem *pRI1 = (PartitionRangeItem *)spec->partStart;
    PartitionRangeItem *pRI2 = (PartitionRangeItem *)spec->partEnd;

    PartitionRangeItemIsValid(vstate->pstate,
                              (PartitionRangeItem *)spec->partEnd);
    compareRc = partition_range_compare(
        vstate->pstate, vstate->cxt, vstate->stmt, vstate->pBy,
        vstate->at_depth, vstate->partNumber, ">", pRI1, pRI2);

    if (0 != compareRc) {
      ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                      errmsg("START greater than END for partition%s%s",
                             vstate->namBuf, vstate->at_depth),
                      errOmitLocation(true),
                      parser_errposition(vstate->pstate, spec->location)));
    }
    compareRc = partition_range_compare(
        vstate->pstate, vstate->cxt, vstate->stmt, vstate->pBy,
        vstate->at_depth, vstate->partNumber, "=", pRI1, pRI2);

    if (0 != compareRc) {
      ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                      errmsg("START equal to END for partition%s%s",
                             vstate->namBuf, vstate->at_depth),
                      errOmitLocation(true),
                      parser_errposition(vstate->pstate, spec->location)));
    }
  }

  if (NIL == vstate->allRangeVals) bAppendRange = true;

  /* check previous first */
  if (!bAppendRange && (spec->partStart && prevBSpec && prevBSpec->partEnd)) {
    /* compare to last */
    int rc = 0;
    PartitionRangeItem *pRI1 = (PartitionRangeItem *)spec->partStart;
    PartitionRangeItem *pRI2 = (PartitionRangeItem *)prevBSpec->partEnd;

    rc = partition_range_compare(vstate->pstate, vstate->cxt, vstate->stmt,
                                 vstate->pBy, vstate->at_depth,
                                 vstate->partNumber, "=", pRI1, pRI2);

    if ((pRI2->partedge == PART_EDGE_INCLUSIVE) &&
        (pRI1->partedge == PART_EDGE_INCLUSIVE) && (1 == rc)) {
      ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                      errmsg(
                          "starting value of partition%s "
                          "overlaps previous range%s",
                          vstate->namBuf, vstate->at_depth),
                      errOmitLocation(true),
                      parser_errposition(vstate->pstate, spec->location)));
    }

    /*
     * If values are equal, but not inclusive to both,
     * then append is possible
     */
    if (1 != rc)
      rc = partition_range_compare(vstate->pstate, vstate->cxt, vstate->stmt,
                                   vstate->pBy, vstate->at_depth,
                                   vstate->partNumber, ">", pRI1, pRI2);

    if (1 == rc) bAppendRange = 1;

    if (rc != 1) {
      ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                      errmsg(
                          "starting value of partition%s "
                          "overlaps previous range%s",
                          vstate->namBuf, vstate->at_depth),
                      errOmitLocation(true),
                      parser_errposition(vstate->pstate, spec->location)));
    }

  } /* end compare to last */

  if (bAppendRange) {
    vstate->allRangeVals = lappend(vstate->allRangeVals, spec);
  } else { /* find position for current range */
    ListCell *lc_all = list_head(vstate->allRangeVals);
    ListCell *lc_allPrev = NULL;
    int compareRc = 0;
    /* set up pieces of error messages */
    char *currPartRI = "Ending";
    char *otherPartPos = "next";

    /* linear search sorted list of range specs:

       While the current start key is >= loop val
       start key advance.

       If current start key < loop val start key,
       then see if curr start key < *previous* loop
       val end key, ie falls in previous range.

       If so, error out, else splice current range in
       after previous.

    */

    for (; lc_all; lc_all = lnext(lc_all)) /* for lc_all */
    {
      PartitionBoundSpec *lcBSpec = (PartitionBoundSpec *)lfirst(lc_all);
      PartitionRangeItem *pRI1 = (PartitionRangeItem *)spec->partStart;
      PartitionRangeItem *pRI2 = (PartitionRangeItem *)lcBSpec->partStart;

      Assert(spec->partStart);

      if (lcBSpec->partStart) {
        compareRc = partition_range_compare(
            vstate->pstate, vstate->cxt, vstate->stmt, vstate->pBy,
            vstate->at_depth, vstate->partNumber, "=", pRI1, pRI2);

        if (-1 == compareRc) break;

        if ((pRI2->partedge == PART_EDGE_INCLUSIVE) &&
            (pRI1->partedge == PART_EDGE_INCLUSIVE) && (1 == compareRc)) {
          currPartRI = "Starting";
          otherPartPos = "previous";
          compareRc = -2;
          break;
        }

        compareRc = partition_range_compare(
            vstate->pstate, vstate->cxt, vstate->stmt, vstate->pBy,
            vstate->at_depth, vstate->partNumber, "<", pRI1, pRI2);

      } else
        /* if first range spec has no start
         * (ie start=MINVALUE) then current start
         * must be after it
         */
        compareRc = 0; /* current > MINVALUE */

      if (-1 == compareRc) break;

      if (1 == compareRc) /* if curr less than loop val */
      {
        /* curr start is less than loop val, so
         * check that curr end is less than loop
         * val start (including equality case)
         */

        pRI1 = (PartitionRangeItem *)spec->partEnd;

        if (!pRI1) {
          /* if current start is less than
           * loop val start but current end is
           * unterminated (ie ending=MAXVALUE)
           * then it must overlap
           */
          currPartRI = "Ending";
          otherPartPos = "next";
          compareRc = -2;
          break;
        }

        compareRc = partition_range_compare(
            vstate->pstate, vstate->cxt, vstate->stmt, vstate->pBy,
            vstate->at_depth, vstate->partNumber, "=", pRI2, pRI1);

        if (-1 == compareRc) break;

        if ((pRI2->partedge == PART_EDGE_INCLUSIVE) &&
            (pRI1->partedge == PART_EDGE_INCLUSIVE) && (1 == compareRc)) {
          currPartRI = "Ending";
          otherPartPos = "next";
          compareRc = -2;
          break;
        }

        compareRc = partition_range_compare(
            vstate->pstate, vstate->cxt, vstate->stmt, vstate->pBy,
            vstate->at_depth, vstate->partNumber, "<", pRI2, pRI1);

        if (-1 == compareRc) break;

        if (1 == compareRc) {
          currPartRI = "Ending";
          otherPartPos = "next";
          compareRc = -2;
          break;
        }

        /* if current is less than loop val and no
         * previous, then append vstate->allRangeVals to
         * the current, instead of vice versa
         */
        if (!lc_allPrev) break;

        lcBSpec = (PartitionBoundSpec *)lfirst(lc_allPrev);

        pRI2 = (PartitionRangeItem *)lcBSpec->partEnd;

        compareRc = partition_range_compare(
            vstate->pstate, vstate->cxt, vstate->stmt, vstate->pBy,
            vstate->at_depth, vstate->partNumber, "=", pRI1, pRI2);

        if (-1 == compareRc) break;

        if ((pRI2->partedge == PART_EDGE_INCLUSIVE) &&
            (pRI1->partedge == PART_EDGE_INCLUSIVE) && (1 == compareRc)) {
          currPartRI = "Starting";
          otherPartPos = "previous";
          compareRc = -2;
          break;
        }

        compareRc = partition_range_compare(
            vstate->pstate, vstate->cxt, vstate->stmt, vstate->pBy,
            vstate->at_depth, vstate->partNumber, "<", pRI1, pRI2);

        if (-1 == compareRc) break;

        if (1 == compareRc) {
          currPartRI = "Starting";
          otherPartPos = "previous";
          compareRc = -2;
          break;
        }
      } /* end if curr less than loop val */

      lc_allPrev = lc_all;
    } /* end for lc_all */

    if (-1 == compareRc) {
      ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                      errmsg("invalid range comparison for partition%s%s",
                             vstate->namBuf, vstate->at_depth),
                      errOmitLocation(true),
                      parser_errposition(vstate->pstate, spec->location)));
    }

    if (-2 == compareRc) {
      ereport(ERROR,
              (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
               errmsg("%s value of partition%s overlaps %s range%s", currPartRI,
                      vstate->namBuf, otherPartPos, vstate->at_depth),
               errOmitLocation(true),
               parser_errposition(vstate->pstate, spec->location)));
    }

    if (lc_allPrev) {
      /* append cell returns new cell, not list */
      lappend_cell(vstate->allRangeVals, lc_allPrev, spec);
    } else /* no previous, so current is start */
      vstate->allRangeVals =
          list_concat(list_make1(spec), vstate->allRangeVals);
  } /* end find position for current range */
  vstate->prevElem = vstate->pElem;
}

Node *coerce_partition_value(Node *node, Oid typid, int32 typmod,
                             PartitionByType partype) {
  Node *out;

  /* If it's a NULL, just directly coerce the value */
  if (IsA(node, Const)) {
    Const *c = (Const *)node;

    if (c->constisnull) {
      c->consttype = typid;
      return node;
    }
  }

  /*
   * We want to cast things directly to the table type. We do
   * not want to have to store a node which coerces this, it's
   * unnecessarily expensive to do it every time.
   */
  out = coerce_to_target_type(NULL, node, exprType(node), typid, typmod,
                              COERCION_EXPLICIT, COERCE_IMPLICIT_CAST, -1);

  /* MPP-3626: better error message */
  if (!out) {
    char exprBuf[10000];
    char *specTName = "";
    char *pparam = "";
    StringInfoData sid;

    /*		elog(ERROR, "cannot coerce partition parameter to column type");
     */

    switch (partype) {
      case PARTTYP_HASH:
        specTName = "HASH ";
        break;
      case PARTTYP_LIST:
        specTName = "LIST ";
        break;
      case PARTTYP_RANGE:
        specTName = "RANGE ";
        break;
      default:
        break;
    }

    /* try to build a printable string of the node value */
    pparam = deparse_expression(
        node, deparse_context_for("partition", InvalidOid), false, false);
    if (pparam) {
      initStringInfo(&sid);
      appendStringInfo(&sid, "(");
      appendStringInfoString(&sid, pparam);
      appendStringInfo(&sid, ") ");

      pfree(pparam);

      exprBuf[0] = '\0';
      snprintf(exprBuf, sizeof(exprBuf), "%s", sid.data);
      pfree(sid.data);

      pparam = exprBuf;
    } else
      pparam = "";

    ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH),
                    errmsg(
                        "cannot coerce %spartition parameter %s"
                        "to column type (%s)",
                        specTName, pparam, format_type_be(typid)),
                    errOmitLocation(true)));
  }

  /* explicit coerce */
  if (IsA(out, FuncExpr) || IsA(out, OpExpr) || IsA(out, CoerceToDomain)) {
    bool isnull;
    Datum d = partition_arg_get_val(out, &isnull);
    Const *c;
    Type typ = typeidType(typid);

    pfree(out);

    c = makeConst(typid, -1, typeLen(typ), d, isnull, typeByVal(typ));
    ReleaseType(typ);
    out = (Node *)c;

    /*
     * coerce again for typmod: if we can't coerce the type mod,
     * we'll error out
     */
    out = coerce_to_target_type(NULL, out, exprType(out), typid, typmod,
                                COERCION_EXPLICIT, COERCE_IMPLICIT_CAST, -1);

    /* be careful, we might just add the coercion function back in! */
    if (IsA(out, FuncExpr) || IsA(out, OpExpr)) out = (Node *)c;
  } else
    Assert(IsA(out, Const));

  return out;
}

static void validate_list_partition(partValidationState *vstate) {
  PartitionValuesSpec *spec;
  Node *n = vstate->spec;
  ListCell *lc;
  List *coltypes = NIL;

  /* just recreate attnum references */
  foreach (lc, vstate->pBy->keys) {
    ListCell *llc2;
    char *colname = strVal(lfirst(lc));
    bool found = false;
    TypeName *typname = NULL;

    foreach (llc2, vstate->cxt->columns) {
      ColumnDef *column = lfirst(llc2);

      if (strcmp(column->colname, colname) == 0) {
        found = true;
        if (!OidIsValid(column->typname->typid)) {
          Type type = typenameType(vstate->pstate, column->typname);
          column->typname->typid = typeTypeId(type);
          ReleaseType(type);
        }
        typname = column->typname;
        break;
      }
    }
    Assert(found);
    coltypes = lappend(coltypes, typname);
  }

  if (!PointerIsValid(n)) {
    ereport(ERROR,
            (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
             errmsg(
                 "missing boundary specification in "
                 "partition%s of type LIST%s",
                 vstate->namBuf, vstate->at_depth),
             errOmitLocation(true),
             parser_errposition(vstate->pstate, vstate->pElem->location)));
  }

  if (!IsA(n, PartitionValuesSpec)) {
    ereport(ERROR,
            (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
             errmsg("invalid boundary specification for LIST partition"),
             errOmitLocation(true),
             parser_errposition(vstate->pstate, vstate->pElem->location)));
  }

  spec = (PartitionValuesSpec *)n;
  if (spec->partValues) {
    ListCell *lc;
    List *newvals = NIL;

    foreach (lc, spec->partValues) {
      ListCell *lc_val = NULL;
      List *vals = lfirst(lc);
      List *tvals = NIL; /* transformed clause */
      int nvals;
      int nparts = list_length(vstate->pBy->keys);
      ListCell *llc2;

      nvals = list_length(vals);

      /* Number of values should be same as specified partition keys */
      if (nvals != nparts) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                        errmsg(
                            "partition key has %i column%s but %i column%s "
                            "specified in VALUES clause",
                            list_length(vstate->pBy->keys),
                            list_length(vstate->pBy->keys) ? "s" : "", nvals,
                            nvals ? "s" : ""),
                        errOmitLocation(true),
                        parser_errposition(vstate->pstate, spec->location)));
      }

      /*
       * Transform expressions
       */
      llc2 = list_head(coltypes);
      foreach (lc_val, vals) {
        Node *node = transformExpr(vstate->pstate, (Node *)lfirst(lc_val));
        TypeName *type = lfirst(llc2);

        node = coerce_partition_value(node, type->typid, type->typmod,
                                      PARTTYP_LIST);

        tvals = lappend(tvals, node);

        if (lnext(llc2))
          llc2 = lnext(llc2);
        else
          llc2 = list_head(coltypes); /* circular */
      }
      Assert(list_length(tvals) == nvals);

      /*
       * Check for duplicate keys
       */
      foreach (lc_val, vstate->allListVals) /* dups across all specs */
      {
        List *already = lfirst(lc_val);
        ListCell *lc2;

        foreach (lc2, already) {
          List *item = lfirst(lc2);

          Assert(IsA(item, List) && list_length(item) == nvals);

          /*
           * Re MPP-17814
           * The lists tvals and item each represent a tuple of nvals
           * attribute values.  If they are equal, the new value (tvals)
           * is in vstate->allListVals, i.e., tvals has been seen before.
           */
          if (equal(tvals, item)) {
            ereport(ERROR,
                    (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                     errmsg(
                         "duplicate VALUES "
                         "in partition%s%s",
                         vstate->namBuf, vstate->at_depth),
                     errOmitLocation(true),
                     parser_errposition(vstate->pstate, spec->location)));
          }
        }
      }
      newvals = list_append_unique(newvals, tvals);
    }
    vstate->allListVals = lappend(vstate->allListVals, newvals);
    spec->partValues = newvals;
  } /* end if spec partvalues */
} /* end validate_list_partition */

static List *transformPartitionStorageEncodingClauses(List *enc) {
  ListCell *lc;
  List *out = NIL;

  /*
   * Test that directives at different levels of subpartitioning do not
   * conflict. A conflict would be the case where a directive appears for the
   * same column but different parameters have been supplied.
   */
  foreach (lc, enc) {
    ListCell *in;
    ColumnReferenceStorageDirective *a = lfirst(lc);
    bool add = true;

    Insist(IsA(a, ColumnReferenceStorageDirective));

    foreach (in, out) {
      ColumnReferenceStorageDirective *b = lfirst(in);

      Insist(IsA(b, ColumnReferenceStorageDirective));

      if (lc == in) continue;

      if (equal(a->column, b->column)) {
        if (!equal(a->encoding, b->encoding))
          ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                          errmsg(
                              "conflicting ENCODING clauses for column "
                              "\"%s\"",
                              strVal(a->column))));

        /*
         * We found an identical directive on the same column. You'd
         * think we should blow up but this is caused by recursive
         * expansion of partitions. Anyway, only add it once.
         */
        add = false;
      }
    }

    if (add) out = lappend(out, a);
  }

  /* validate and transform each encoding clauses */
  foreach (lc, out) {
    ColumnReferenceStorageDirective *c = lfirst(lc);

    c->encoding = transformStorageEncodingClause(c->encoding);
  }

  return out;
}

static void split_encoding_clauses(List *encs, List **non_def,
                                   ColumnReferenceStorageDirective **def) {
  ListCell *lc;

  foreach (lc, encs) {
    ColumnReferenceStorageDirective *c = lfirst(lc);

    Insist(IsA(c, ColumnReferenceStorageDirective));

    if (c->deflt) {
      if (*def)
        elog(ERROR,
             "DEFAULT COLUMN ENCODING clause specified more than "
             "once for partition");
      *def = c;
    } else
      *non_def = lappend(*non_def, c);
  }
}

static void merge_partition_encoding(ParseState *pstate, PartitionElem *elem,
                                     List *penc) {
  List *elem_nondefs = NIL;
  List *part_nondefs = NIL;
  ColumnReferenceStorageDirective *elem_def = NULL;
  ColumnReferenceStorageDirective *part_def = NULL;
  ListCell *lc;
  AlterPartitionCmd *pc;

  /*
   * First of all, we shouldn't proceed if this partition isn't AOCO
   */

  /*
   * Yes, I am as surprised as you are that this is how we represent the WITH
   * clause here.
   */
  pc = (AlterPartitionCmd *)elem->storeAttr;
  if (pc) {
    if (elem->colencs)
      ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                      errmsg(
                          "ENCODING clause only supported with "
                          "column oriented partitions"),
                      parser_errposition(pstate, elem->location)));
    else
      return; /* nothing more to do */
  }

  /*
   * If the specific partition has no specific column encoding, just
   * set it to the partition level default and we're done.
   */
  if (!elem->colencs) {
    elem->colencs = penc;
    return;
  }

  /*
   * Fixup the actual column encoding clauses for this specific partition
   * element.
   *
   * Rules:
   *
   * 1. If an element level clause mentions a specific column, do not override
   * it.
   * 2. Clauses at the partition configuration level which mention a column
   * not already mentioned at the element level, are applied to the element.
   * 3. If an element level default clause exists, we're done.
   * 4. If a partition configuration level default clause exists, apply it to
   * the element level.
   * 5. We're done.
   */

  /* Split specific clauses and default clauses from both our lists */
  split_encoding_clauses(elem->colencs, &elem_nondefs, &elem_def);
  split_encoding_clauses(penc, &part_nondefs, &part_def);

  /* Add clauses from part_nondefs if the columns are not already mentioned */
  foreach (lc, part_nondefs) {
    ListCell *lc2;
    ColumnReferenceStorageDirective *pd = lfirst(lc);
    bool found = false;

    foreach (lc2, elem_nondefs) {
      ColumnReferenceStorageDirective *ed = lfirst(lc2);

      if (equal(pd->column, ed->column)) {
        found = true;
        break;
      }
    }

    if (!found) elem->colencs = lappend(elem->colencs, pd);
  }

  if (elem_def) return;

  if (part_def) elem->colencs = lappend(elem->colencs, part_def);
}

int validate_partition_spec(ParseState *pstate, CreateStmtContext *cxt,
                            CreateStmtBase *stmt, PartitionBy *pBy,
                            char *at_depth, int partNumber) {
  PartitionSpec *pSpec;
  char namBuf[NAMEDATALEN];
  List *partElts;
  ListCell *lc = NULL;
  List *allPartNames = NIL;
  partValidationState *vstate;
  PartitionElem *pDefaultElem = NULL;
  int partno = 0;
  List *enc_cls = NIL;

  vstate = palloc0(sizeof(partValidationState));

  vstate->pstate = pstate;
  vstate->cxt = cxt;
  vstate->stmt = stmt;
  vstate->pBy = pBy;
  vstate->at_depth = at_depth;
  vstate->partNumber = partNumber;

  Assert(pBy);
  pSpec = (PartitionSpec *)pBy->partSpec;

  /*
   * Find number of partitions in the specification, and match it up
   * with the PARTITIONS clause if partitioned by HASH, and
   * determine the subpartition specifications.  Perform basic error
   * checking on boundary specifications.
   */

  /* NOTE: a top-level PartitionSpec never has a subSpec, but a
   * PartitionSpec derived from an inline SubPartition
   * specification might have one
   */

  /* track previous boundary spec to check for this subtle problem:

  (
  partition aa start (2007,1) end (2008,2),
  partition bb start (2008,2) end (2009,3)
  );
  This declaration would create four partitions:
  1. named aa, starting at 2007, 1
  2. unnamed, ending at 2008, 2
  3. named bb, starting at 2008, 2
  4. unnamed, ending at 2009, 3

  Warn user if they do this, since they probably wanted
  1. named aa, starting at 2007, 1 and ending at 2008, 2
  2. named bb, starting at 2008, 2 and ending at 2009, 3

  The extra comma between the start and end is the problem.

  */

  if (pBy->partType == PARTTYP_RANGE) preprocess_range_spec(vstate);

  partElts = pSpec->partElem;

  /*
   * If this is a RANGE partition, we might have gleaned some encoding clauses
   * already, because preprocess_range_spec() looks at partition elements.
   */
  enc_cls = pSpec->enc_clauses;

  foreach (lc, partElts) {
    PartitionElem *pElem = (PartitionElem *)lfirst(lc);
    PartitionBoundSpec *pBSpec = NULL;

    if (!IsA(pElem, PartitionElem)) {
      Insist(IsA(pElem, ColumnReferenceStorageDirective));
      enc_cls = lappend(enc_cls, lfirst(lc));
      continue;
    }
    vstate->pElem = pElem;

    if (pSpec->istemplate && pElem->colencs)
      ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                      errmsg(
                          "partition specific ENCODING clause not supported in "
                          "SUBPARTITION TEMPLATE"),
                      parser_errposition(pstate, pElem->location)));

    /*
     * We've done all possible expansions so now number the
     * partition elements so that we can set the position when
     * adding the configuration to the catalog.
     */
    pElem->partno = ++partno;

    if (pElem) {
      pBSpec = (PartitionBoundSpec *)pElem->boundSpec;
      vstate->spec = (Node *)pBSpec;

      /* handle all default partition cases:

         HASH partitioned tables cannot have DEFAULT partitions.
         Can only have a single DEFAULT partition.
         Default partitions cannot have a boundary specification.

      */

      if (pElem->isDefault) {
        Assert(pElem->partName); /* default partn must have a name */
        snprintf(namBuf, sizeof(namBuf), " \"%s\"", strVal(pElem->partName));

        if (pDefaultElem) {
          ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                          errmsg(
                              "multiple default partitions are not "
                              "allowed"),
                          errOmitLocation(true),
                          parser_errposition(pstate, pElem->location)));
        }

        pDefaultElem = pElem; /* save the default */

        if (PARTTYP_HASH == pBy->partType) {
          ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                          errmsg(
                              "invalid use of DEFAULT partition "
                              "for partition%s of type HASH%s",
                              namBuf, at_depth),
                          errOmitLocation(true),
                          parser_errposition(pstate, pElem->location)));
        }

        if (pBSpec) {
          ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                          errmsg(
                              "invalid use of boundary specification "
                              "for DEFAULT partition%s%s",
                              namBuf, at_depth),
                          errOmitLocation(true),
                          parser_errposition(pstate, pElem->location)));
        }

      } /* end if is default */

      if (pElem->partName) {
        bool doit = true;

        snprintf(namBuf, sizeof(namBuf), " \"%s\"", strVal(pElem->partName));
        /*
         * We might have expanded an EVERY clause here. If so, just
         * add the first partition name.
         */
        if (doit) {
          if (allPartNames && list_member(allPartNames, pElem->partName)) {
            ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                            errmsg(
                                "duplicate partition name "
                                "for partition%s%s",
                                namBuf, at_depth),
                            errOmitLocation(true),
                            parser_errposition(pstate, pElem->location)));
          }
          allPartNames = lappend(allPartNames, pElem->partName);
        }
      } else {
        if (pElem->AddPartDesc)
          snprintf(namBuf, sizeof(namBuf), "%s", pElem->AddPartDesc);
        else
          snprintf(namBuf, sizeof(namBuf), " number %d", partno);
      }
    } else {
      if (pElem->AddPartDesc)
        snprintf(namBuf, sizeof(namBuf), "%s", pElem->AddPartDesc);
      else
        snprintf(namBuf, sizeof(namBuf), " number %d", partno);
    }

    /* don't have to validate default partition boundary specs */
    if (pElem->isDefault) continue;

    vstate->namBuf = namBuf;

    switch (pBy->partType) {
      case PARTTYP_HASH:
        if (vstate->spec) {
          char *specTName = "RANGE";

          if (IsA(vstate->spec, PartitionValuesSpec)) specTName = "LIST";

          ereport(ERROR,
                  (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                   errmsg(
                       "invalid use of %s boundary specification "
                       "in partition%s of type HASH%s",
                       specTName, vstate->namBuf, vstate->at_depth),
                   errOmitLocation(true),
                   /* MPP-4249: use value spec location if have one */
                   ((IsA(vstate->spec, PartitionValuesSpec))
                        ? parser_errposition(
                              pstate,
                              ((PartitionValuesSpec *)vstate->spec)->location)
                        :
                        /* else use boundspec */
                        parser_errposition(
                            pstate,
                            ((PartitionBoundSpec *)vstate->spec)->location))));
        }
        break;
      case PARTTYP_RANGE:
        validate_range_partition(vstate);
        break;

      case PARTTYP_LIST:
        validate_list_partition(vstate);
        break;

      case PARTTYP_REFERENCE: /* for future use... */
      default:
        ereport(
            ERROR,
            (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
             errmsg("unknown partition type %d %s", pBy->partType, at_depth),
             errOmitLocation(true), parser_errposition(pstate, pBy->location)));
        break;
    }
  } /* end foreach(lc, partElts) */

  pSpec->enc_clauses = transformPartitionStorageEncodingClauses(enc_cls);

  foreach (lc, partElts) {
    PartitionElem *elem = (PartitionElem *)lfirst(lc);

    if (!IsA(elem, PartitionElem)) continue;

    merge_partition_encoding(pstate, elem, pSpec->enc_clauses);
  }

  /* validate_range_partition might have changed some boundaries */
  if (pBy->partType == PARTTYP_RANGE)
    pSpec->partElem = sort_range_elems(pBy->keyopclass, partElts);

  if (partNumber > -1 && partno != partNumber)
    ereport(ERROR,
            (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
             errmsg(
                 "PARTITIONS \"%d\" must match \"%d\" elements "
                 "in specification%s",
                 partNumber, partno, vstate->at_depth),
             errOmitLocation(true), parser_errposition(pstate, pBy->location)));

  if (vstate->allRangeVals) list_free(vstate->allRangeVals);

  if (allPartNames) list_free(allPartNames);

  return partno;
} /* end validate_partition_spec */

static Const *flatten_partition_val(Node *node, Oid target_type) {
  if (IsA(node, Const))
    return (Const *)node;
  else {
    Datum res;
    Oid curtyp;
    Const *c;
    Type typ = typeidType(target_type);
    int32 typmod = ((Form_pg_type)GETSTRUCT(typ))->typtypmod;
    int16 typlen = ((Form_pg_type)GETSTRUCT(typ))->typlen;
    bool typbyval = ((Form_pg_type)GETSTRUCT(typ))->typbyval;
    bool isnull;

    ReleaseType(typ);

    curtyp = exprType(node);
    Assert(OidIsValid(curtyp));

    if (curtyp != target_type && OidIsValid(target_type)) {
      node = coerce_type(NULL, node, curtyp, target_type, typmod,
                         COERCION_EXPLICIT, COERCE_IMPLICIT_CAST, -1);

      if (!PointerIsValid(node))
        elog(ERROR, "could not coerce partitioning parameter");
    }

    res = partition_arg_get_val(node, &isnull);
    c = makeConst(target_type, typmod, typlen, res, isnull, typbyval);

    return c;
  }
}

/*
 * Get the actual value from the expression. There are only a limited range
 * of cases we must cover because the parser guarantees constant input.
 */
Datum partition_arg_get_val(Node *node, bool *isnull) {
  switch (nodeTag(node)) {
    case T_FuncExpr: {
      /* must have been cast to the operator. */
      FuncExpr *fe = (FuncExpr *)node;
      Datum d = PointerGetDatum(NULL);

      switch (list_length(fe->args)) {
        case 1: {
          Datum d1 = partition_arg_get_val(linitial(fe->args), isnull);

          if (!*isnull) d = OidFunctionCall1(fe->funcid, d1);
        } break;
        case 2: {
          bool null1, null2;
          Datum d1 = partition_arg_get_val(linitial(fe->args), &null1);
          Datum d2 = partition_arg_get_val(lsecond(fe->args), &null2);

          *isnull = null1 || null2;
          if (!*isnull) d = OidFunctionCall2(fe->funcid, d1, d2);
        } break;
        case 3: {
          bool null1, null2, null3;

          Datum d1 = partition_arg_get_val(linitial(fe->args), &null1);
          Datum d2 = partition_arg_get_val(lsecond(fe->args), &null2);
          Datum d3 = partition_arg_get_val(lthird(fe->args), &null3);

          *isnull = null1 || null2 || null3;
          if (!*isnull) d = OidFunctionCall3(fe->funcid, d1, d2, d3);
        } break;
        default:
          elog(ERROR,
               "unexpected number of coercion function "
               "arguments: %d",
               list_length(fe->args));
          break;
      }
      return d;
    } break;
    case T_OpExpr: {
      OpExpr *op = (OpExpr *)node;
      Datum d = 0;

      if (!OidIsValid(op->opfuncid)) op->opfuncid = get_opcode(op->opno);

      switch (list_length(op->args)) {
        case 1: {
          Datum d1 = partition_arg_get_val(linitial(op->args), isnull);
          if (!*isnull) d = OidFunctionCall1(op->opfuncid, d1);
        } break;
        case 2: {
          bool null1, null2;
          Datum d1 = partition_arg_get_val(linitial(op->args), &null1);
          Datum d2 = partition_arg_get_val(lsecond(op->args), &null2);
          *isnull = null1 || null2;
          if (!*isnull) d = OidFunctionCall2(op->opfuncid, d1, d2);
        } break;
        default:
          elog(ERROR,
               "unexpected number of arguments for operator function: %d",
               list_length(op->args));
      }
      return d;
    }
      *isnull = false;
      break;
    case T_RelabelType: {
      RelabelType *n = (RelabelType *)node;

      return partition_arg_get_val((Node *)n->arg, isnull);
    } break;
    case T_Const:
      *isnull = ((Const *)node)->constisnull;
      return ((Const *)node)->constvalue;
      break;
    case T_CoerceToDomain: {
      CoerceToDomain *c = (CoerceToDomain *)node;
      return partition_arg_get_val((Node *)c->arg, isnull);
    } break;
    default:
      elog(ERROR, "unknown partitioning argument type: %d", nodeTag(node));
      break;
  }
  return 0; /* quieten GCC */
}

/*
 * Evaluate a basic operator expression from a partitioning specification.
 * The expression will only be an op expr but the sides might contain
 * a coercion function. The underlying value will be a simple constant,
 * however.
 *
 * If restypid is non-NULL and *restypid is set to InvalidOid, we tell the
 * caller what the return type of the operator is. If it is anything but
 * InvalidOid, coerce the operation's result to that type.
 */
static Datum eval_basic_opexpr(ParseState *pstate, List *oprname, Node *leftarg,
                               Node *rightarg, bool *typbyval, int16 *typlen,
                               Oid *restypid, int location) {
  Datum res = 0;
  Datum lhs = 0;
  Datum rhs = 0;
  OpExpr *opexpr;
  bool byval;
  int16 len;
  bool need_typ_info = (PointerIsValid(restypid) && *restypid == InvalidOid);
  bool isnull;
  Oid oprcode;
  int fetchCount;

  opexpr = (OpExpr *)make_op(pstate, oprname, leftarg, rightarg, location);

  oprcode = caql_getoid_plus(NULL, &fetchCount, NULL,
                             cql("SELECT oprcode FROM pg_operator "
                                 " WHERE oid = :1 ",
                                 ObjectIdGetDatum(opexpr->opno)));

  if (!fetchCount) /* should not fail */
    elog(ERROR, "cache lookup failed for operator %u", opexpr->opno);
  opexpr->opfuncid = oprcode;

  lhs = partition_arg_get_val((Node *)linitial(opexpr->args), &isnull);
  if (!isnull) {
    rhs = partition_arg_get_val((Node *)lsecond(opexpr->args), &isnull);
    if (!isnull) res = OidFunctionCall2(opexpr->opfuncid, lhs, rhs);
  }

  /* If the caller supplied a target result type, coerce if necesssary */
  if (PointerIsValid(restypid)) {
    if (OidIsValid(*restypid)) {
      if (*restypid != opexpr->opresulttype) {
        Expr *e;
        Type typ = typeidType(opexpr->opresulttype);
        int32 typmod;
        Const *c;

        c = makeConst(opexpr->opresulttype, -1, typeLen(typ), res, isnull,
                      typeByVal(typ));
        ReleaseType(typ);

        typ = typeidType(*restypid);
        typmod = ((Form_pg_type)GETSTRUCT(typ))->typtypmod;
        ReleaseType(typ);

        e = (Expr *)coerce_type(NULL, (Node *)c, opexpr->opresulttype,
                                *restypid, typmod, COERCION_EXPLICIT,
                                COERCE_IMPLICIT_CAST, -1);

        res = partition_arg_get_val((Node *)e, &isnull);
      }
    } else {
      *restypid = opexpr->opresulttype;
    }
  } else {
    return res;
  }

  if (need_typ_info || !PointerIsValid(typbyval)) {
    Type typ = typeidType(*restypid);

    byval = typeByVal(typ);
    len = typeLen(typ);

    if (PointerIsValid(typbyval)) {
      Assert(PointerIsValid(typlen));
      *typbyval = byval;
      *typlen = len;
    }

    ReleaseType(typ);
  } else {
    byval = *typbyval;
    len = *typlen;
  }

  res = datumCopy(res, byval, len);
  return res;
}

/*
 * XXX: this is awful, rewrite
 */
static int partition_range_every(ParseState *pstate, PartitionBy *pBy,
                                 List *coltypes, char *at_depth,
                                 partValidationState *vstate) {
  PartitionSpec *pSpec;
  char namBuf[NAMEDATALEN];
  int numElts = 0;
  List *partElts;
  ListCell *lc = NULL;
  ListCell *lc_prev = NULL;
  List *stenc = NIL;

  Assert(pBy);
  Assert(pBy->partType == PARTTYP_RANGE);

  pSpec = (PartitionSpec *)pBy->partSpec;

  if (pSpec) /* no bound spec for default partition */
  {
    partElts = pSpec->partElem;
    stenc = pSpec->enc_clauses;

    lc = list_head(partElts);
  }

  for (; lc;) /* foreach */
  {
    PartitionElem *pElem = (PartitionElem *)lfirst(lc);

    PartitionBoundSpec *pBSpec = NULL;
    int numCols = 0;
    int colCnt = 0;
    Const *everyCnt;
    PartitionRangeItem *pRI_Start = NULL;
    PartitionRangeItem *pRI_End = NULL;
    PartitionRangeItem *pRI_Every = NULL;
    ListCell *lc_Start = NULL;
    ListCell *lc_End = NULL;
    ListCell *lc_Every = NULL;
    Node *pStoreAttr = NULL;
    List *allNewCols;
    List *allNewPartns = NIL;
    List *lastval = NIL;
    bool do_every_param_test = true;

    pBSpec = NULL;

    if (pElem && IsA(pElem, ColumnReferenceStorageDirective)) {
      stenc = lappend(stenc, pElem);
      goto l_next_iteration;
    }

    numElts++;

    if (!pElem)
      goto l_EveryLoopEnd;
    else {
      pBSpec = (PartitionBoundSpec *)pElem->boundSpec;

      if (!pBSpec) goto l_EveryLoopEnd;

      pStoreAttr = pElem->storeAttr;

      if (pElem->partName) {
        snprintf(namBuf, sizeof(namBuf), " \"%s\"", strVal(pElem->partName));
      } else {
        snprintf(namBuf, sizeof(namBuf), " number %d", numElts);
      }

      if (pElem->isDefault) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                        errmsg(
                            "invalid use of boundary specification "
                            "for DEFAULT partition%s%s",
                            namBuf, at_depth),
                        errOmitLocation(true),
                        parser_errposition(pstate, pElem->location)));
      } /* end if is default */

      /* MPP-3541: invalid use of LIST spec */
      if (!IsA(pBSpec, PartitionBoundSpec)) {
        ereport(ERROR,
                (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                 errmsg(
                     "invalid use of LIST boundary specification "
                     "in partition%s of type RANGE%s",
                     namBuf, at_depth),
                 errOmitLocation(true),
                 /* MPP-4249: use value spec location if have one */
                 ((IsA(pBSpec, PartitionValuesSpec))
                      ? parser_errposition(
                            pstate, ((PartitionValuesSpec *)pBSpec)->location)
                      :
                      /* else use invalid parsestate/postition */
                      parser_errposition(NULL, 0))));
      }

      Assert(IsA(pBSpec, PartitionBoundSpec));
    }

    /* have a valid range bound spec */

    if (!pBSpec->partEvery) goto l_EveryLoopEnd;

    /* valid EVERY needs a START and END */
    if (!(pBSpec->partStart && pBSpec->partEnd)) {
      ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                      errmsg(
                          "EVERY clause in partition%s "
                          "requires START and END%s",
                          namBuf, at_depth),
                      errOmitLocation(true),
                      parser_errposition(pstate, pBSpec->location)));
    }

    /* MPP-6297: check for WITH (tablename=name) clause
     * [magic to make dump/restore work by ignoring EVERY]
     */
    if (pStoreAttr && ((AlterPartitionCmd *)pStoreAttr)->arg1) {
      ListCell *prev_lc = NULL;
      ListCell *def_lc = NULL;
      List *pWithList = (List *)(((AlterPartitionCmd *)pStoreAttr)->arg1);
      bool bTablename = false;

      foreach (def_lc, pWithList) {
        DefElem *pDef = (DefElem *)lfirst(def_lc);

        /* get the tablename from the WITH, then remove this
         * element from the list */
        if (0 == strcmp(pDef->defname, "tablename")) {
          /* if the string isn't quoted you get a typename ? */
          if (!IsA(pDef->arg, String))
            ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
                            errmsg("invalid tablename specification")));

          bTablename = true;
          bool need_free_value = false;
          char *widthname_str = defGetString(pDef, &need_free_value);
          pBSpec->pWithTnameStr = pstrdup(widthname_str);
          if (need_free_value) {
            pfree(widthname_str);
            widthname_str = NULL;
          }

          AssertImply(need_free_value, NULL == widthname_str);

          pWithList = list_delete_cell(pWithList, def_lc, prev_lc);
          ((AlterPartitionCmd *)pStoreAttr)->arg1 = (Node *)pWithList;
          break;
        }
        prev_lc = def_lc;
      } /* end foreach */

      if (bTablename) goto l_EveryLoopEnd;
    }

    everyCnt = make_const(pstate, makeInteger(1), -1);

    pRI_Start = (PartitionRangeItem *)pBSpec->partStart;
    pRI_End = (PartitionRangeItem *)pBSpec->partEnd;
    pRI_Every = (PartitionRangeItem *)pBSpec->partEvery;

    numCols = list_length(pRI_Every->partRangeVal);

    if ((numCols != list_length(pRI_Start->partRangeVal)) ||
        (numCols != list_length(pRI_End->partRangeVal))) {
      ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                      errmsg(
                          "mismatch between EVERY, START and END "
                          "in partition%s%s",
                          namBuf, at_depth),
                      errOmitLocation(true),
                      parser_errposition(pstate, pBSpec->location)));
    }

    /* XXX XXX: need to save prev value of
       every_num * (every_cnt * n3t) for first column only
    */

    for (;;) /* loop until exceed end */
    {
      ListCell *coltype = list_head(coltypes);
      ListCell *lclastval = list_head(lastval);
      List *curval = NIL;

      int sqlRc = 0;
      lc_Start = list_head(pRI_Start->partRangeVal);
      lc_End = list_head(pRI_End->partRangeVal);
      lc_Every = list_head(pRI_Every->partRangeVal);
      colCnt = numCols;
      allNewCols = NIL;

      for (; lc_Start && lc_End && lc_Every;) /* for all cols */
      {
        Node *n1 = lfirst(lc_Start);
        Node *n2 = lfirst(lc_End);
        Node *n3 = lfirst(lc_Every);
        TypeName *type = lfirst(coltype);
        char *compare_op = (1 == colCnt) ? "<" : "<=";
        Node *n1t, *n2t, *n3t;
        Datum res;
        Const *c;
        Const *newend;
        List *oprmul, *oprplus, *oprcompare, *ltop;
        Oid restypid;
        Type typ;
        char *outputstr;
        Oid coltypid = type->typid;

        if (!OidIsValid(coltypid)) {
          Type t = typenameType(pstate, type);
          coltypid = type->typid = typeTypeId(t);
          ReleaseType(t);
        }

        oprmul = lappend(NIL, makeString("*"));
        oprplus = lappend(NIL, makeString("+"));
        oprcompare = lappend(NIL, makeString(compare_op));
        ltop = lappend(NIL, makeString("<"));

        n1t = transformExpr(pstate, n1);
        n1t = coerce_type(NULL, n1t, exprType(n1t), coltypid, type->typmod,
                          COERCION_EXPLICIT, COERCE_IMPLICIT_CAST, -1);
        n1t = (Node *)flatten_partition_val(n1t, coltypid);

        n2t = transformExpr(pstate, n2);
        n2t = coerce_type(NULL, n2t, exprType(n2t), coltypid, type->typmod,
                          COERCION_EXPLICIT, COERCE_IMPLICIT_CAST, -1);
        n2t = (Node *)flatten_partition_val(n2t, coltypid);

        n3t = transformExpr(pstate, n3);
        n3t = (Node *)flatten_partition_val(n3t, exprType(n3t));

        Assert(IsA(n3t, Const));
        Assert(IsA(n2t, Const));
        Assert(IsA(n1t, Const));

        /* formula is n1t + (every_cnt * every_num * n3t) [<|<=] n2t */

        /* every_cnt * n3t */
        restypid = InvalidOid;
        res = eval_basic_opexpr(pstate, oprmul, (Node *)everyCnt, n3t, NULL,
                                NULL, &restypid, ((A_Const *)n3)->location);
        typ = typeidType(restypid);
        c = makeConst(restypid, -1, typeLen(typ), res, false, typeByVal(typ));
        ReleaseType(typ);

        /*
         * n1t + (...)
         * NOTE: order is important because several useful operators,
         * like date + interval, only have a built in function when
         * the order is this way (the reverse is implemented using
         * an SQL function which we cannot evaluate at the moment).
         */
        restypid = exprType(n1t);
        res = eval_basic_opexpr(pstate, oprplus, n1t, (Node *)c, NULL, NULL,
                                &restypid, ((A_Const *)n1)->location);
        typ = typeidType(restypid);
        newend =
            makeConst(restypid, -1, typeLen(typ), res, false, typeByVal(typ));
        ReleaseType(typ);

        /*
         * Now we must detect a few conditions.
         *
         * We must reject every() parameters which do not significantly
         * increment the starting value. For example:
         *
         * 1: start ('2008-01-01') end ('2010-01-01') every('0 days')
         *
         * We'll detect this case on the second iteration by observing
         * that the current partition end is no greater than the
         * previous.
         *
         * We must also consider:
         *
         * 2: start (1) end (10) every(0.5)
         * 3: start (1) end (10) every(1.5)
         *
         * The problem here is that 1.5 and 2.5 will be rounded to 2 and
         * 3 respectively. This means they look sane to the code but
         * they aren't. So, we must test that:
         *
         *   cast(1 + 0.5 as int) != (1 + 0.5)::numeric
         *
         * If we make it past this point, we still might see a current
         * value less than the previous value. That would be caused by
         * an overflow of the type which is undetected by the type
         * itself. Most types detect overflow but time and timetz do
         * not. So, if we're beyond the second iteration and detect and
         * overflow, error out.
         */
        /* do it on the first iteration */
        if (!lclastval) {
          List *opreq = lappend(NIL, makeString("="));
          Datum uncast;
          Datum iseq;
          Const *tmpconst;
          Oid test_typid = InvalidOid;

          uncast =
              eval_basic_opexpr(pstate, oprplus, n1t, (Node *)c, NULL, NULL,
                                &test_typid, ((A_Const *)n1)->location);

          typ = typeidType(test_typid);
          tmpconst = makeConst(test_typid, -1, typeLen(typ), uncast, false,
                               typeByVal(typ));
          ReleaseType(typ);

          iseq = eval_basic_opexpr(NULL, opreq, (Node *)tmpconst,
                                   (Node *)newend, NULL, NULL, NULL, -1);

          if (!DatumGetBool(iseq))
            ereport(
                ERROR,
                (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                 errmsg("EVERY parameter produces ambiguous partition rule"),
                 errOmitLocation(true),
                 parser_errposition(pstate, ((A_Const *)n3)->location)));
        }

        if (lclastval) {
          Datum res2;
          Oid tmptyp = InvalidOid;

          /*
           * now test for case 2 and 3 above: ensure that
           * the cast value is equal to the uncast value.
           */

          res2 = eval_basic_opexpr(pstate, ltop, (Node *)lfirst(lclastval),
                                   (Node *)newend, NULL, NULL, &tmptyp,
                                   ((A_Const *)n3)->location);

          if (!DatumGetBool(res2)) {
            /*
             * Second iteration: parameter hasn't increased the
             * current end from the old end.
             */
            if (do_every_param_test) {
              ereport(
                  ERROR,
                  (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                   errmsg("EVERY parameter too small"), errOmitLocation(true),
                   parser_errposition(pstate, ((A_Const *)n3)->location)));
            } else {
              /*
               * We got a smaller value but later than we thought
               * so it must be an overflow.
               */
              ereport(
                  ERROR,
                  (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                   errmsg("END parameter not reached before type overflows"),
                   errOmitLocation(true),
                   parser_errposition(pstate, ((A_Const *)n2)->location)));
            }
          }
        }

        curval = lappend(curval, newend);

        /* get the string for the calculated type */
        {
          Oid ooutput;
          bool isvarlena;
          FmgrInfo finfo;

          getTypeOutputInfo(restypid, &ooutput, &isvarlena);
          fmgr_info(ooutput, &finfo);

          if (isvarlena) res = PointerGetDatum(PG_DETOAST_DATUM(res));

          outputstr = OutputFunctionCall(&finfo, res);
        }

        /* the comparison */
        restypid = InvalidOid;
        res = eval_basic_opexpr(pstate, oprcompare, (Node *)newend, n2t, NULL,
                                NULL, &restypid, ((A_Const *)n2)->location);

        /* XXX XXX: also check stop flag.
           and free up prev if stop is true or current > end.
         */

        if (!DatumGetBool(res)) {
          if (outputstr) pfree(outputstr);

          sqlRc = 0;
          break;
        } else {
          allNewCols = lappend(allNewCols, pstrdup(outputstr));

          if (outputstr) pfree(outputstr);

          sqlRc = 1;
        }
        lc_Start = lnext(lc_Start);
        lc_End = lnext(lc_End);
        lc_Every = lnext(lc_Every);
        coltype = lnext(coltype);
        if (lclastval) lclastval = lnext(lclastval);

        colCnt--;
      } /* end for all cols */

      if (lc_Start || lc_End ||
          lc_Every) { /* allNewCols is incomplete - don't use */
        if (allNewCols) list_free_deep(allNewCols);
      } else {
        allNewPartns = lappend(allNewPartns, allNewCols);
      }

      if (!sqlRc) break;

      everyCnt->constvalue++;
      /* if we have lastval set, the every test will have been done */

      if (lastval) do_every_param_test = false;
      lastval = curval;
    } /* end loop until exceed end */

  l_EveryLoopEnd:

    if (allNewPartns) Assert(pBSpec); /* check for default partitions... */

    if (pBSpec) pBSpec->everyGenList = allNewPartns;

  l_next_iteration:
    lc_prev = lc;
    lc = lnext(lc);
  } /* end foreach */

  pSpec->enc_clauses = stenc;

  return 1;
} /* end partition_range_every */

/*
 * Add or override any column reference storage directives inherited from the
 * parent table.
 */
static void merge_part_column_encodings(CreateStmtBase *cs, List *stenc) {
  ListCell *lc;
  List *parentencs = NIL;
  List *others = NIL;
  List *finalencs = NIL;

  /* Don't waste time unnecessarily */
  if (!stenc) return;

  /*
   * First, split the table elements into column reference storage directives
   * and everything else.
   */
  foreach (lc, cs->tableElts) {
    Node *n = lfirst(lc);

    if (IsA(n, ColumnReferenceStorageDirective))
      parentencs = lappend(parentencs, n);
    else
      others = lappend(others, n);
  }

  /*
   * Now build a final set of storage directives for this partition. Start
   * with stenc and then add everything from parentencs which doesn't appear
   * in that. This means we prefer partition level directives over those
   * directives specified for the parent.
   *
   * We must copy stenc, since list_concat may modify it destructively.
   */
  finalencs = list_copy(stenc);

  foreach (lc, parentencs) {
    bool found = false;
    ListCell *lc2;
    ColumnReferenceStorageDirective *p = lfirst(lc);

    if (p->deflt) continue;

    foreach (lc2, finalencs) {
      ColumnReferenceStorageDirective *f = lfirst(lc2);

      if (f->deflt) continue;

      if (equal(f->column, p->column)) {
        found = true;
        break;
      }
    }
    if (!found) finalencs = lappend(finalencs, p);
  }

  /*
   * Finally, make sure we don't propagate any conflicting clauses in the
   * ColumnDef.
   */
  foreach (lc, others) {
    Node *n = lfirst(lc);

    if (IsA(n, ColumnDef)) {
      ColumnDef *c = (ColumnDef *)n;
      ListCell *lc2;

      foreach (lc2, finalencs) {
        ColumnReferenceStorageDirective *r = lfirst(lc2);

        if (r->deflt) continue;

        if (strcmp(strVal(r->column), c->colname) == 0) {
          c->encoding = NIL;
          break;
        }
      }
    }
  }

  cs->tableElts = list_concat(others, finalencs);
}

static void make_child_node(CreateStmtBase *stmt, CreateStmtContext *cxt,
                            char *relname, PartitionBy *curPby, Node *newSub,
                            Node *pRuleCatalog, Node *pPostCreate,
                            Node *pConstraint, Node *pStoreAttr, char *prtstr,
                            bool bQuiet, List *stenc) {
  RangeVar *parent_tab_name = makeNode(RangeVar);
  RangeVar *child_tab_name = makeNode(RangeVar);
  CreateStmtBase child_tab_stmt;
  memset(&child_tab_stmt, 0, sizeof(CreateStmtBase));

  parent_tab_name->catalogname = cxt->relation->catalogname;
  parent_tab_name->schemaname = cxt->relation->schemaname;
  parent_tab_name->relname = cxt->relation->relname;
  parent_tab_name->location = -1;

  child_tab_name->catalogname = cxt->relation->catalogname;
  child_tab_name->schemaname = cxt->relation->schemaname;
  child_tab_name->relname = relname;
  child_tab_name->location = -1;

  child_tab_stmt.relation = child_tab_name;
  child_tab_stmt.is_part_child = true;
  child_tab_stmt.is_add_part = stmt->is_add_part;

  if (!bQuiet)
    ereport(NOTICE, (errmsg(
                        "%s will create partition \"%s\" for "
                        "table \"%s\"",
                        cxt->stmtType, child_tab_name->relname,
                        cxt->relation->relname)));

  /* set the "Post Create" rule if it exists */
  child_tab_stmt.postCreate = pPostCreate;

  /*
   * Deep copy the parent's table elements.
   *
   * XXX The copy may be unnecessary, but it is safe.
   *
   * Previously, some following code updated constraint names
   * in the tableElts to assure uniqueness and handle issues
   * with FKs (?).  This required a copy.
   *
   * However, forcing a name change at this level overrides any
   * user-specified constraint names, so we don't do one here
   * any more.
   */
  child_tab_stmt.tableElts = copyObject(stmt->tableElts);

  merge_part_column_encodings(&child_tab_stmt, stenc);

  /* Hash partitioning special case. */
  if (pConstraint &&
      ((enable_partition_rules && curPby->partType == PARTTYP_HASH) ||
       curPby->partType != PARTTYP_HASH))
    child_tab_stmt.tableElts = lappend(child_tab_stmt.tableElts, pConstraint);

  /*
   * XXX XXX: inheriting the parent causes a headache in
   * transformDistributedBy, since it assumes the parent exists
   * already.  Just add an "ALTER TABLE...INHERIT parent" after
   * the create child table
   */
  /*child_tab_stmt->inhRelations = list_make1(parent_tab_name); */
  child_tab_stmt.inhRelations = list_copy(stmt->inhRelations);

  child_tab_stmt.constraints = copyObject(stmt->constraints);
  child_tab_stmt.options = stmt->options;

  /* allow WITH clause for appendonly tables */
  if (pStoreAttr) {
    AlterPartitionCmd *psa_apc = (AlterPartitionCmd *)pStoreAttr;

    /* Options */
    if (psa_apc->arg1) child_tab_stmt.options = (List *)psa_apc->arg1;
    /* Tablespace from parent (input CreateStmt)... */
    if (psa_apc->arg2 && *strVal(psa_apc->arg2))
      child_tab_stmt.tablespacename = strVal(psa_apc->arg2);
  }
  /* ...or tablespace from root. */
  if (!child_tab_stmt.tablespacename && stmt->tablespacename)
    child_tab_stmt.tablespacename = stmt->tablespacename;

  child_tab_stmt.oncommit = stmt->oncommit;
  child_tab_stmt.distributedBy = stmt->distributedBy;

  /* use the newSub as the partitionBy if the current
   * partition elem had an inline subpartition declaration
   */
  child_tab_stmt.partitionBy = (Node *)newSub;

  child_tab_stmt.relKind = RELKIND_RELATION;

  /*
   * Adjust tablespace name for the CREATE TABLE via ADD PARTITION. (MPP-8047)
   *
   * The way we traverse the hierarchy, parents are visited before children, so
   * we can usually pick up tablespace from the parent relation.  If the child
   * is a top-level branch, though, we take the tablespace from the root.
   * Ultimately, we take the tablespace as specified in the command, or, if none
   * was specified, the one from the root paritioned table.
   */
  if (!child_tab_stmt.tablespacename) {
    Oid poid = RangeVarGetRelid(cxt->relation, true,
                                false /*allowHcatalog*/); /* parent branch */

    if (!poid) {
      poid = RangeVarGetRelid(
          stmt->relation, true,
          false /*alloweHcatalog*/); /* whole partitioned table */
    }
    if (poid) {
      Relation prel = RelationIdGetRelation(poid);
      child_tab_stmt.tablespacename =
          get_tablespace_name(prel->rd_rel->reltablespace);
      RelationClose(prel);
    }
  }

  bool isChildTableExt = false;
  if (pStoreAttr) {
    if (((AlterPartitionCmd *)pStoreAttr)->format)
      isChildTableExt = true;
    else
      isChildTableExt = false;
  } else {
    isChildTableExt = cxt->isExternalTable;
  }

  if (isChildTableExt) {
    CreateExternalStmt *childStmt = makeNode(CreateExternalStmt);
    childStmt->base = child_tab_stmt;
    if (pStoreAttr) {
      childStmt->format = pstrdup(((AlterPartitionCmd *)pStoreAttr)->format);
      childStmt->base.options = ((AlterPartitionCmd *)pStoreAttr)->arg1;
      childStmt->iswritable = true;
      childStmt->exttypedesc = makeNode(ExtTableTypeDesc);
    } else {
      childStmt->exttypedesc = copyObject(cxt->exttypedesc);
      childStmt->format = pstrdup(cxt->format);
      childStmt->iswritable = cxt->iswritable;
    }
    // compatible with hive dir for partition table
    if (cxt->isExternalTable == false || cxt->parentPath == NULL)
    	childStmt->parentPath = pstrdup(parent_tab_name->relname);
    else {
    	char *path = (char *)palloc(sizeof(char) *
    			(strlen(cxt->parentPath)
    					+ strlen(parent_tab_name->relname)
							+ 1 +1));
    	sprintf(path, "%s/%s",cxt->parentPath, parent_tab_name->relname);
    	childStmt->parentPath = pstrdup(path);
    	pfree(path);
    }
    cxt->alist = lappend(cxt->alist, childStmt);
  } else {
    CreateStmt *childStmt = makeNode(CreateStmt);
    childStmt->base = child_tab_stmt;
    cxt->alist = lappend(cxt->alist, childStmt);
  }

  /*
   * ALTER TABLE for inheritance after CREATE TABLE ...
   * XXX: Think of a better way.
   */
  if (1) {
    AlterTableCmd *atc = makeNode(AlterTableCmd);
    AlterTableStmt *ats = makeNode(AlterTableStmt);
    InheritPartitionCmd *ipc = makeNode(InheritPartitionCmd);

    if (isChildTableExt) {
      ats->relkind = OBJECT_EXTTABLE;
      ats->greenWay = true;
    } else {
      ats->relkind = OBJECT_TABLE;
      ats->greenWay = true;
    }

    /* alter table child inherits parent */
    atc->subtype = AT_AddInherit;
    ipc->parent = parent_tab_name;
    atc->def = (Node *)ipc;
    ats->relation = child_tab_name;
    ats->cmds = list_make1((Node *)atc);

    /* this is the deepest we're going, add the partition rules */
    if (1) {
      AlterTableCmd *atc2 = makeNode(AlterTableCmd);

      /* alter table add child to partition set */
      atc2->subtype = AT_PartAddInternal;
      atc2->def = (Node *)curPby;
      ats->cmds = lappend(ats->cmds, atc2);
    }
    cxt->alist = lappend(cxt->alist, ats);
  }
}

static void expand_hash_partition_spec(PartitionBy *pBy) {
  PartitionSpec *spec;
  long i;
  long max;
  List *elem = NIL;
  A_Const *con;

  Assert(pBy->partType == PARTTYP_HASH);
  Assert(pBy->partSpec == NULL);

  spec = makeNode(PartitionSpec);

  con = (A_Const *)pBy->partNum;
  Assert(IsA(&con->val, Integer));
  max = intVal(&con->val);

  for (i = 0; i < max; i++) {
    PartitionElem *el = makeNode(PartitionElem);

    elem = lappend(elem, el);
  }
  spec->partElem = elem;
  pBy->partSpec = (Node *)spec;
}

static bool partition_col_walker(Node *node, void *context) {
  if (node == NULL) return false;

  if (IsA(node, PartitionSpec)) {
    PartitionSpec *s = (PartitionSpec *)node;

    if (partition_col_walker(s->subSpec, context)) return true;

    return partition_col_walker((Node *)s->partElem, context);
  } else if (IsA(node, PartitionElem)) {
    PartitionElem *el = (PartitionElem *)node;

    return partition_col_walker(el->subSpec, context);

  } else if (IsA(node, PartitionBy)) {
    PartitionBy *p = (PartitionBy *)node;
    ListCell *lc;
    part_col_cxt *cxt = (part_col_cxt *)context;

    foreach (lc, p->keys) {
      char *colname = strVal(lfirst(lc));
      ListCell *llc;

      foreach (llc, cxt->cols) {
        char *col = lfirst(llc);

        if (strcmp(col, colname) == 0)
          ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                          errmsg(
                              "column \"%s\" specified in multiple "
                              "partitioning keys",
                              colname),
                          errOmitLocation(true),
                          parser_errposition(cxt->pstate, p->location)));
      }
      cxt->cols = lappend(cxt->cols, colname);
    }

    if (partition_col_walker(p->subPart, context)) return true;
    return partition_col_walker(p->partSpec, context);
  }

  return expression_tree_walker(node, partition_col_walker, context);
}

/*
 * transformPartitionBy() - transform a partitioning clause attached to a CREATE
 * TABLE statement.
 *
 * An example clause:
 *
 * PARTITION BY col1
 *   SUBPTN  BY col2 SUBTEMPLATE (Spec2),
 *   SUBPTN  BY col3 SUBTEMPLATE (Spec3)
 * (Spec1)
 *
 * becomes a chain of PartitionBy structs, each with a PartitionSpec:
 *
 *   pBy -> (col1, spec1, NULL subspec)
 *    |
 *    v
 *   pBy -> (col2, spec2, NULL subspec)
 *    |
 *    v
 *   pBy -> (col3, spec3, NULL subspec)
 *
 * This struct is easy to process recursively.  However, for the syntax:
 *
 * PARTITION BY col1
 *   SUBPTN  BY col2
 *   SUBPTN  BY col3
 * (
 * PTN AA (SUB CCC (SUB EE, SUB FF), SUB DDD (SUB EE, SUB FF))
 * PTN BB (SUB CCC (SUB EE, SUB FF), SUB DDD (SUB EE, SUB FF))
 * )
 *
 * the struct is more like:
 *
 *   pBy -> (col1, spec1, spec2->spec3)
 *    |
 *    v
 *   pBy -> (col2, NULL spec, NULL subspec)
 *    |
 *    v
 *   pBy -> (col3, NULL spec, NULL subspec)
 *
 *
 * We need to move the subpartition specifications to the correct spots.
 *
 */
static void transformPartitionBy(ParseState *pstate, CreateStmtContext *cxt,
                                 CreateStmtBase *stmt, Node *partitionBy,
                                 GpPolicy *policy) {
  Oid snamespaceid;
  char *snamespace;
  int partDepth; /* depth (starting at zero, but display at 1) */
  char depthstr[NAMEDATALEN];
  char *at_depth = "";
  char at_buf[NAMEDATALEN];
  int partNumber = -1;
  int partno = 1;
  int everyno = 0;
  List *partElts = NIL;
  ListCell *lc = NULL;
  PartitionBy *pBy;           /* the current partitioning clause */
  PartitionBy *psubBy = NULL; /* child of current */
  char prtstr[NAMEDATALEN];
  Oid accessMethodId = InvalidOid;
  bool lookup_opclass;
  Node *prevEvery;
  ListCell *lc_anp = NULL;
  List *key_attnums = NIL;
  List *key_attnames = NIL;
  part_col_cxt pcolcxt;
  List *stenc = NIL;

  if (NULL == partitionBy) return;

  pBy = (PartitionBy *)partitionBy;

  /* disallow composite partition keys */
  if (1 < list_length(pBy->keys)) {
    ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                    errmsg("Composite partition keys are not allowed")));
  }

  partDepth = pBy->partDepth;
  partDepth++; /* increment depth for subpartition */

  if (0 < gp_max_partition_level && partDepth > gp_max_partition_level) {
    ereport(ERROR,
            (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
             errmsg("Exceeded the maximum allowed level of partitioning of %d",
                    gp_max_partition_level)));
  }

  snprintf(depthstr, sizeof(depthstr), "%d", partDepth);

  if (pBy->partDepth != 0) {
    /*
     * Only mention the depth 2 and greater to aid in debugging
     * subpartitions
     */
    snprintf(at_buf, sizeof(at_buf), " (at depth %d)", partDepth);
    at_depth = at_buf;
  } else
    pBy->parentRel = copyObject(stmt->relation);

  /* set the depth for the immediate subpartition */
  if (pBy->subPart) {
    psubBy = (PartitionBy *)pBy->subPart;
    psubBy->partDepth = partDepth;
    if (((PartitionBy *)pBy->subPart)->parentRel == NULL)
      ((PartitionBy *)pBy->subPart)->parentRel = copyObject(pBy->parentRel);
  }

  /*
   * Derive the number of partitions from the PARTITIONS clause.
   */
  if (pBy->partNum) {
    A_Const *con = (A_Const *)pBy->partNum;

    Assert(IsA(&con->val, Integer));

    partNumber = intVal(&con->val);

    if (pBy->partType != PARTTYP_HASH) {
      ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                      errmsg("%sPARTITIONS clause requires a HASH partition%s",
                             pBy->partDepth != 0 ? "SUB" : "", at_depth),
                      errOmitLocation(true),
                      parser_errposition(pstate, pBy->location)));
    }

    if (partNumber < 1) {
      ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                      errmsg("%sPARTITIONS cannot be less than one%s",
                             pBy->partDepth != 0 ? "SUB" : "", at_depth),
                      errOmitLocation(true),
                      parser_errposition(pstate, pBy->location)));
    }
  }

  /*
   * The recursive nature of this code means that if we're processing a
   * sub partition rule, the opclass may have been looked up already.
   */
  lookup_opclass = list_length(pBy->keyopclass) == 0;

  if ((list_length(pBy->keys) > 1) && (pBy->partType == PARTTYP_RANGE)) {
    ereport(ERROR,
            (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
             errmsg(
                 "too many columns for RANGE partition%s -- "
                 "only one column is allowed.",
                 at_depth),
             errOmitLocation(true), parser_errposition(pstate, pBy->location)));
  }

  /* validate keys */
  foreach (lc, pBy->keys) {
    Value *colval = lfirst(lc);
    char *colname = strVal(colval);
    ListCell *columns;
    bool found = false;
    Oid typeid = InvalidOid;
    Oid opclass = InvalidOid;
    int i = 0;

    foreach (columns, cxt->columns) {
      ColumnDef *column = (ColumnDef *)lfirst(columns);
      Assert(IsA(column, ColumnDef));

      i++;
      if (strcmp(column->colname, colname) == 0) {
        found = true;

        if (list_member_int(key_attnums, i))
          ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                          errmsg(
                              "column \"%s\" specified more than once "
                              "in partitioning key",
                              colname),
                          errOmitLocation(true),
                          parser_errposition(pstate, pBy->location)));

        key_attnums = lappend_int(key_attnums, i);
        Insist(IsA(colval, String));
        key_attnames = lappend(key_attnames, colval);

        if (lookup_opclass) {
          typeid = column->typname->typid;

          if (!OidIsValid(typeid)) {
            Type type = typenameType(pstate, column->typname);
            column->typname->typid = typeid = typeTypeId(type);
            ReleaseType(type);
          }
        }
        break;
      }
    }

    if (!found) {
      ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN),
                      errmsg("column \"%s\" does not exist in relation \"%s\"",
                             colname, cxt->relation->relname),
                      errOmitLocation(true),
                      parser_errposition(pstate, pBy->location)));
    }

    if (lookup_opclass) {
      /* get access method ID for this partition type */
      switch (pBy->partType) {
        case PARTTYP_HASH:
          accessMethodId = HASH_AM_OID;
          break;
        case PARTTYP_RANGE:
        case PARTTYP_LIST:
          accessMethodId = BTREE_AM_OID;
          break;
        default:
          elog(ERROR, "unknown partitioning type %i", pBy->partType);
          break;
      }

      opclass = GetDefaultOpClass(typeid, accessMethodId);

      if (!OidIsValid(opclass))
        ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT),
                        errmsg("data type %s has no default operator class",
                               format_type_be(typeid)),
                        errOmitLocation(true)));
      pBy->keyopclass = lappend_oid(pBy->keyopclass, opclass);
    }
  }

  /* Have partitioning keys; check for violating unique constraints */
  foreach (lc, cxt->ixconstraints) {
    ListCell *ilc = NULL;

    Constraint *ucon = (Constraint *)lfirst(lc);
    Insist(ucon->keys != NIL);

    foreach (ilc, key_attnames) {
      Value *partkeyname = (Value *)lfirst(ilc);

      if (!list_member(ucon->keys, partkeyname)) {
        char *what = NULL;
        switch (ucon->contype) {
          case CONSTR_PRIMARY:
            what = "PRIMARY KEY";
            break;
          case CONSTR_UNIQUE:
            what = "UNIQUE";
            break;
          default:
            elog(ERROR,
                 "unexpected constraint type in internal transformation");
            break;
        }
        ereport(
            ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE),
             errmsg(
                 "%s constraint must contain all columns in the "
                 "partition key",
                 what),
             errhint(
                 "Include column \"%s\" in the %s constraint or create "
                 "a part-wise UNIQUE index after creating the table instead.",
                 strVal(partkeyname), what)));
      }
    }
  }

  /* No further use for key_attnames, so clean up. */
  if (key_attnames) {
    list_free(key_attnames);
  }
  key_attnames = NIL;

  /* see if there are any duplicate column references */
  if (0) /* MPP-3988: allow same column in multiple partitioning
                  * keys at different levels */
    partition_col_walker((Node *)pBy, &pcolcxt);

  if (pBy->partType == PARTTYP_HASH) {
    if (pBy->partSpec == NULL) {
      if (pBy->partNum == NULL)
        ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                        errmsg(
                            "hash partition requires PARTITIONS clause "
                            "or partition specification"),
                        errOmitLocation(true),
                        parser_errposition(pstate, pBy->location)));

      /*
       * Users don't have to specify a partition specification
       * for HASH.  If they didn't, create one so that the rest
       * of the code can generate some valid partition children.
       */
      expand_hash_partition_spec(pBy);
    }
  }

  if (pBy->partSpec) {
    partNumber =
        validate_partition_spec(pstate, cxt, stmt, pBy, at_depth, partNumber);
    stenc = ((PartitionSpec *)pBy->partSpec)->enc_clauses;
  }

  /*
   * We'd better have some partitions to look at by now
   */
  if (partNumber < 1) {
    ereport(ERROR,
            (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
             errmsg("no partitions specified at depth %d", partDepth),
             errOmitLocation(true), parser_errposition(pstate, pBy->location)));
  }

  /* Clear error position. */
  pstate->p_breadcrumb.node = NULL;

  /*
   * Determine namespace and name to use for the child table.
   */
  snamespaceid = RangeVarGetCreationNamespace(cxt->relation);
  snamespace = get_namespace_name(snamespaceid);

  /* set up the partition specification element list if it exists */
  if (pBy->partSpec) {
    PartitionSpec *pSpec = (PartitionSpec *)pBy->partSpec;
    PartitionSpec *subSpec = (PartitionSpec *)pSpec->subSpec;

    /*
     * If the current specification has a subSpec, then that is
     * the specification for the child, so we'd better have a
     * SUBPARTITION BY clause for the child. The sub specification
     * cannot contain a partition specification of its own.
     */
    if (subSpec) {
      if (NULL == psubBy) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                        errmsg(
                            "missing SUBPARTITION BY clause for "
                            "subpartition specification%s",
                            at_depth),
                        errOmitLocation(true),
                        parser_errposition(pstate, pBy->location)));
      }
      if (psubBy && psubBy->partSpec) {
        ereport(ERROR,
                (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                 errmsg("subpartition specification conflict%s", at_depth),
                 errOmitLocation(true),
                 parser_errposition(pstate, psubBy->location)));
      }
      psubBy->partSpec = (Node *)subSpec;
    }
    partElts = pSpec->partElem;
  }

  Assert(partNumber > 0);

  /*
   * We must iterate through the elements in this way to support HASH
   * partitioning, which likely has no partitioning elements.
   */
  lc = list_head(partElts);

  prevEvery = NULL;
  /* Iterate through each partition element */
  for (partno = 0; partno < partNumber; partno++) {
    PartitionBy *newSub = NULL;
    PartitionElem *pElem = lc ? (PartitionElem *)lfirst(lc) : NULL;

    Node *pRuleCatalog = NULL;
    Node *pPostCreate = NULL;
    Node *pConstraint = NULL;
    Node *pStoreAttr = NULL;
    char *relname = NULL;
    PartitionBoundSpec *pBSpec = NULL;
    Node *every = NULL;
    PartitionBy *curPby = NULL;
    bool bQuiet = false;
    char *pWithTname = NULL;
    List *colencs = NIL;

    /* silence partition name messages if really generating a
     * subpartition template
     */
    if (pBy->partQuiet == PART_VERBO_NOPARTNAME) bQuiet = true;

    if (pElem) {
      pStoreAttr = pElem->storeAttr;
      colencs = pElem->colencs;

      if (pElem->boundSpec && IsA(pElem->boundSpec, PartitionBoundSpec)) {
        pBSpec = (PartitionBoundSpec *)pElem->boundSpec;
        every = pBSpec->partEvery;
        pWithTname = pBSpec->pWithTnameStr;

        if (prevEvery && every) {
          if (equal(prevEvery, every))
            everyno++;
          else {
            everyno = 1; /* reset */
            lc_anp = list_head(pBSpec->everyGenList);
          }
        } else if (every) {
          everyno++;
          if (!lc_anp) lc_anp = list_head(pBSpec->everyGenList);
        } else {
          everyno = 0;
          lc_anp = NULL;
        }
      }

      if (pElem->partName) {
        /*
         * Use the partition name as part of the child
         * table name
         */
        char *pName = strVal(pElem->partName);

        snprintf(prtstr, sizeof(prtstr), "prt_%s", pName);
      } else {
        /*
         * For the case where we don't have a partition name,
         * don't worry about EVERY.
         */
        if (pElem->rrand)
          /* make a random name for ALTER TABLE ... ADD PARTITION */
          snprintf(prtstr, sizeof(prtstr), "prt_r%lu",
                   pElem->rrand + partno + 1);
        else
          snprintf(prtstr, sizeof(prtstr), "prt_%d", partno + 1);
      }

      if (pElem->subSpec) {
        /*
         * If the current partition element has a subspec,
         * then that is the spec for the child.  So we'd
         * better have a SUBPARTITION BY clause for the child,
         * and that clause cannot contain a SUBPARTITION
         * TEMPLATE (which is a spec).  Note that we might
         * have just updated the subBy partition spec with the
         * subspec of the current spec, which would be a
         * conflict.  If the psubBy has a NULL spec, we make a
         * copy of the node and update it -- the subSpec
         * becomes the specification for the child.  And if
         * the subspec has a subspec it gets handled at the
         * top of this loop as the subspec of the current spec
         * when transformPartitionBy is re-invoked for the
         * child table.
         */
        if (NULL == psubBy) {
          ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                          errmsg(
                              "missing SUBPARTITION BY clause "
                              "for subpartition specification%s",
                              at_depth),
                          errOmitLocation(true),
                          parser_errposition(pstate, pElem->location)));
        }

        if (psubBy && psubBy->partSpec) {
          Assert(((PartitionSpec *)psubBy->partSpec)->istemplate);

          ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                          errmsg(
                              "subpartition configuration conflicts "
                              "with subpartition template"),
                          errOmitLocation(true),
                          parser_errposition(pstate, psubBy->location)));
        }

        if (!((PartitionSpec *)pElem->subSpec)->istemplate &&
            !gp_allow_non_uniform_partitioning_ddl) {
          ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                          errmsg(
                              "Multi-level partitioned tables without "
                              "templates are not supported")));
        }

        newSub = makeNode(PartitionBy);

        newSub->partType = psubBy->partType;
        newSub->keys = psubBy->keys;
        newSub->partNum = psubBy->partNum;
        newSub->subPart = psubBy->subPart;
        newSub->partSpec = pElem->subSpec; /* use the subspec */
        newSub->partDepth = psubBy->partDepth;
        newSub->partQuiet = pBy->partQuiet;

        if (pElem->subSpec) /* get a good location for error msg */
        {
          /* use subspec location */
          newSub->location = ((PartitionSpec *)pElem->subSpec)->location;
        } else {
          newSub->location = pElem->location;
        }
      }
    } else
      snprintf(prtstr, sizeof(prtstr), "prt_%d", partno + 1);

    /* MPP-6297: check for WITH (tablename=name) clause
     * [only for dump/restore, set deep in the guts of
     * partition_range_every...]
     */
    if (pWithTname) {
      relname = pWithTname;
      prtstr[0] = '\0';
    } else if (pStoreAttr && ((AlterPartitionCmd *)pStoreAttr)->arg1) {
      /* MPP-6297, MPP-7661, MPP-7514: check for WITH
       * (tablename=name) clause (AGAIN!) because the pWithTname
       * is only set for an EVERY clause, and we might not have
       * an EVERY clause.
       */

      ListCell *prev_lc = NULL;
      ListCell *def_lc = NULL;
      List *pWithList = (List *)(((AlterPartitionCmd *)pStoreAttr)->arg1);

      foreach (def_lc, pWithList) {
        DefElem *pDef = (DefElem *)lfirst(def_lc);

        /* get the tablename from the WITH, then remove this
         * element from the list */
        if (0 == strcmp(pDef->defname, "tablename")) {
          /* if the string isn't quoted you get a typename ? */
          if (!IsA(pDef->arg, String))
            ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
                            errmsg("invalid tablename specification")));

          bool need_free_value = false;
          char *relname_str = defGetString(pDef, &need_free_value);
          relname = pstrdup(relname_str);
          if (need_free_value) {
            pfree(relname_str);
            relname_str = NULL;
          }

          AssertImply(need_free_value, NULL == relname_str);

          prtstr[0] = '\0';
          pWithList = list_delete_cell(pWithList, def_lc, prev_lc);
          ((AlterPartitionCmd *)pStoreAttr)->arg1 = (Node *)pWithList;
          break;
        }
        prev_lc = def_lc;
      } /* end foreach */
    }

    if (strlen(prtstr))
      relname = ChooseRelationName(cxt->relation->relname, depthstr, /* depth */
                                   prtstr, /* part spec */
                                   snamespaceid, NULL);

    /* check non-uniform bucketnum options */
    if (pStoreAttr && ((AlterPartitionCmd *)pStoreAttr)->arg1) {
      List *pWithList = (List *)(((AlterPartitionCmd *)pStoreAttr)->arg1);
      int bucketnum = policy->bucketnum;
      int child_bucketnum =
          GetRelOpt_bucket_num_fromOptions(pWithList, bucketnum);

      if (child_bucketnum != bucketnum)
        ereport(ERROR, (errcode(ERRCODE_GP_FEATURE_NOT_SUPPORTED),
                        errmsg(
                            "distribution policy for \"%s\" "
                            "must be the same as that for \"%s\"",
                            relname, cxt->relation->relname)));
    }

    /* XXX: temporarily add rule creation code for debugging */

    /* now that we have the child table name, make the rule */
    if (!(pElem && pElem->isDefault)) {
      ListCell *lc_rule = NULL;
      int everycount = every ? ((PartitionRangeItem *)every)->everycount : 0;
      List *allRules = make_partition_rules(
          pstate, cxt, stmt, partitionBy, pElem, at_depth, relname, partno + 1,
          partNumber, everyno, everycount, &lc_anp, true);

      if (allRules) lc_rule = list_head(allRules);

      if (lc_rule) {
        pConstraint = lfirst(lc_rule);

        if (pConstraint) {
          StringInfoData sid;
          Constraint *pCon = makeNode(Constraint);

          initStringInfo(&sid);

          appendStringInfo(&sid, "%s_%s", relname, "check");

          pCon->contype = CONSTR_CHECK;
          pCon->name = sid.data;
          pCon->raw_expr = pConstraint;
          pCon->cooked_expr = NULL;
          pCon->indexspace = NULL;

          pConstraint = (Node *)pCon;
        }

        lc_rule = lnext(lc_rule);

        if (lc_rule) {
          pRuleCatalog = lfirst(lc_rule);

          /* look for a Rule statement to run after the
           * relation is created
           * (see DefinePartitionedRelation in tablecmds.c)
           */
          lc_rule = lnext(lc_rule);
        }

        if (lc_rule) {
          List *pL1 = NULL;
          int *pInt1;
          int *pInt2;

          pInt1 = (int *)palloc(sizeof(int));
          pInt2 = (int *)palloc(sizeof(int));

          *pInt1 = partno + 1;
          *pInt2 = everyno;

          pL1 = list_make1(relname); /* rule name */
          pL1 = lappend(pL1, pInt1); /* partition position */
          pL1 = lappend(pL1, pInt2); /* every position */

          if (pElem && pElem->partName) {
            char *pName = strVal(pElem->partName);

            pL1 = lappend(pL1, pName);
          } else
            pL1 = lappend(pL1, NULL);

          pL1 = lappend(pL1, relname);                /* child name */
          pL1 = lappend(pL1, cxt->relation->relname); /* parent name */

          if (enable_partition_rules)
            pPostCreate = (Node *)list_make2(lfirst(lc_rule), pL1);
          else
            pPostCreate = NULL;
        }
      }
    }

    curPby = makeNode(PartitionBy);

    {
      PartitionSpec *tmppspec = makeNode(PartitionSpec);

      /* selectively copy pBy */
      curPby->partType = pBy->partType;

      curPby->keys = key_attnums;

      curPby->keyopclass = copyObject(pBy->keyopclass);
      curPby->partNum = copyObject(pBy->partNum);

      if (pElem) {
        PartitionSpec *_spec = (PartitionSpec *)pBy->partSpec;
        tmppspec->partElem = list_make1(copyObject(pElem));
        tmppspec->istemplate = _spec->istemplate;
        tmppspec->enc_clauses = copyObject(stenc);
      }

      curPby->partSpec = (Node *)tmppspec;
      curPby->partDepth = pBy->partDepth;
      curPby->partQuiet = pBy->partQuiet;
      curPby->parentRel = copyObject(pBy->parentRel);
    }

    if (!newSub) newSub = copyObject(psubBy);

    if (newSub) newSub->parentRel = copyObject(pBy->parentRel);

    make_child_node(stmt, cxt, relname, curPby, (Node *)newSub, pRuleCatalog,
                    pPostCreate, pConstraint, pStoreAttr, prtstr, bQuiet,
                    colencs);

    if (pBSpec) prevEvery = pBSpec->partEvery;

    if (lc) lc = lnext(lc);
  }

  /* nefarious: we need to keep the "top"
   * partition by statement because
   * analyze.c:do_parse_analyze needs to find
   * it to re-order the ALTER statements.
   * (see cdbpartition.c:atpxPart_validate_spec)
   */
  if ((pBy->partDepth > 0) && (pBy->bKeepMe != true)) {
    /* we don't need this any more */
    stmt->partitionBy = NULL;
    stmt->is_part_child = true;
  }

} /* end transformPartitionBy */

static void transformFKConstraints(ParseState *pstate, CreateStmtContext *cxt,
                                   bool skipValidation, bool isAddConstraint) {
  ListCell *fkclist;

  if (cxt->fkconstraints == NIL) return;

  /*
   * If CREATE TABLE or adding a column with NULL default, we can safely
   * skip validation of the constraint.
   */
  if (skipValidation) {
    foreach (fkclist, cxt->fkconstraints) {
      FkConstraint *fkconstraint = (FkConstraint *)lfirst(fkclist);

      fkconstraint->skip_validation = true;
    }
  }

  /*
   * For CREATE TABLE or ALTER TABLE ADD COLUMN, gin up an ALTER TABLE ADD
   * CONSTRAINT command to execute after the basic command is complete. (If
   * called from ADD CONSTRAINT, that routine will add the FK constraints to
   * its own subcommand list.)
   *
   * Note: the ADD CONSTRAINT command must also execute after any index
   * creation commands.  Thus, this should run after
   * transformIndexConstraints, so that the CREATE INDEX commands are
   * already in cxt->alist.
   */
  if (!isAddConstraint) {
    AlterTableStmt *alterstmt = makeNode(AlterTableStmt);

    alterstmt->relation = cxt->relation;
    alterstmt->cmds = NIL;
    alterstmt->relkind = OBJECT_TABLE;

    foreach (fkclist, cxt->fkconstraints) {
      FkConstraint *fkconstraint = (FkConstraint *)lfirst(fkclist);
      AlterTableCmd *altercmd = makeNode(AlterTableCmd);

      altercmd->subtype = AT_ProcessedConstraint;
      altercmd->name = NULL;
      altercmd->def = (Node *)fkconstraint;
      alterstmt->cmds = lappend(alterstmt->cmds, altercmd);
    }

    cxt->alist = lappend(cxt->alist, alterstmt);
  }
}

/*
 * transformIndexStmt -
 *	  transforms the qualification of the index statement
 *
 * If do_part is true, build create index statements for our children.
 */
static Query *transformIndexStmt(ParseState *pstate, IndexStmt *stmt,
                                 List **extras_before, List **extras_after) {
  Query *qry;
  RangeTblEntry *rte = NULL;
  ListCell *l;
  Oid idxOid;
  Oid nspOid;

  qry = makeNode(Query);
  qry->commandType = CMD_UTILITY;

  char *graph = stmt->relation->schemaname ? pstrdup(stmt->relation->schemaname) : NULL;
  char *ele = stmt->relation->relname;
  int isGraph = parseAndTransformAsGraph(pstate, stmt->relation);
  if(isGraph) {
    stmt->graphele = makeRangeVar(stmt->relation->schemaname, graph, ele, -1);
    Oid dboid = GetCatalogId(stmt->relation->catalogname);
    Oid namespaceId = LookupNamespaceId(stmt->relation->schemaname, dboid);
    if(isGraph == 2 /* as edge*/) {
      Oid relid = caql_getoid(
          NULL,
          cql("SELECT oid FROM pg_class "
            " WHERE relname = :1 "
            " AND relnamespace = :2 ",
            CStringGetDatum(stmt->relation->relname),
            ObjectIdGetDatum(namespaceId)));

      Relation attrelation = heap_open(AttributeRelationId, RowExclusiveLock);
      cqContext cqc;
      int colNum = caql_getcount(
          NULL,
                cql("SELECT COUNT(*) FROM pg_attribute "
                  " WHERE attrelid = :1 AND attnum > :2",
                  ObjectIdGetDatum(relid), Int32GetDatum(0)));

      cqContext *pcqCtx = caql_beginscan(
          caql_addrel(cqclr(&cqc), attrelation),
          cql("SELECT * FROM pg_attribute "
            " WHERE attrelid = :1 AND attnum > :2",
            ObjectIdGetDatum(relid), Int32GetDatum(0)));
      HeapTuple attributeTuple;
      char **colnames = palloc0(colNum * sizeof(char*));
      while (HeapTupleIsValid(attributeTuple = caql_getnext(pcqCtx))) {
        Form_pg_attribute att = (Form_pg_attribute) GETSTRUCT(attributeTuple);
        colnames[att->attnum - 1] = pstrdup(NameStr(att->attname));
      }
      caql_endscan(pcqCtx);
      heap_close(attrelation, RowExclusiveLock);
      Relation ElabelRelation = heap_open(ElabelRelationId, RowExclusiveLock);
      HeapTuple elabelTuple= caql_getfirst(caql_addrel(cqclr(&cqc), ElabelRelation),
                                           cql("SELECT * FROM skylon_elabel"
          " WHERE elabelname = :1 AND schemaname = :2",
          CStringGetDatum(ele), CStringGetDatum(stmt->relation->schemaname)));
      Form_skylon_elabel elabel = (Form_skylon_elabel) GETSTRUCT(elabelTuple);
      Relation VlabelAttRelation = heap_open(VlabelAttrRelationId, RowExclusiveLock);
      int srcNum =
          caql_getcount(
          NULL,
          cql("SELECT COUNT(*) FROM skylon_vlabel_attribute "
             " WHERE vlabelname = :1 AND schemaname = :2 "
             "AND primaryrank > :3",
             CStringGetDatum(elabel->fromvlabel.data), CStringGetDatum(stmt->relation->schemaname), Int32GetDatum(0)));
      int dstNum = caql_getcount(
        NULL,
        cql("SELECT COUNT(*) FROM skylon_vlabel_attribute "
          " WHERE vlabelname = :1 AND schemaname = :2 "
            "AND primaryrank > :3",
          CStringGetDatum(elabel->tovlabel.data), CStringGetDatum(stmt->relation->schemaname), Int32GetDatum(0)));
      int primaryNum = srcNum + dstNum;
      heap_close(VlabelAttRelation, RowExclusiveLock);
      heap_close(ElabelRelation, RowExclusiveLock);
      Relation elabelAttRelation = heap_open(ElabelAttrRelationId, RowExclusiveLock);
      int eattnum = caql_getcount(
          NULL,
              cql("SELECT COUNT(*) FROM skylon_elabel_attribute "
                " WHERE elabelname = :1 AND schemaname = :2 "
                  "AND rank > :3",
                  CStringGetDatum(ele), CStringGetDatum(stmt->relation->schemaname), Int32GetDatum(0)));
      char **attnames = palloc0(eattnum * sizeof(char*));
      pcqCtx = caql_beginscan(
          caql_addrel(cqclr(&cqc), elabelAttRelation),
                        cql("SELECT * FROM skylon_elabel_attribute "
                          " WHERE elabelname = :1 AND schemaname = :2 "
                            "AND rank > :3",
                            CStringGetDatum(ele), CStringGetDatum(stmt->relation->schemaname), Int32GetDatum(0)));
      while (HeapTupleIsValid(attributeTuple = caql_getnext(pcqCtx))) {
        Form_skylon_elabel_attribute att = (Form_skylon_elabel_attribute) GETSTRUCT(attributeTuple);
        attnames[att->rank - 1] = pstrdup(NameStr(att->attrname));
      }
      caql_endscan(pcqCtx);
      heap_close(elabelAttRelation, RowExclusiveLock);
      ListCell *cell;
      foreach(cell, stmt->indexParams) {
        IndexElem *ele = (IndexElem *)lfirst(cell);
        for(int i = 0; i < eattnum; i++) {
          if(strcmp(attnames[i], ele->name) == 0) {
            Value *attnum = makeInteger(0);
            attnum->val.ival = i + 1;
            stmt->graphIndexAttnum = lappend(stmt->graphIndexAttnum, attnum);
            break;
          }
        }
      }
      foreach(cell, stmt->indexIncludingParams) {
        IndexElem *ele = (IndexElem *)lfirst(cell);
        for(int i = 0; i < eattnum; i++) {
          if(strcmp(attnames[i], ele->name) == 0) {
            Value *attnum = makeInteger(0);
            attnum->val.ival = i + 1;
            stmt->graphIncludeAttnum = lappend(stmt->graphIncludeAttnum, attnum);
            break;
          }
        }
      }
      List *newIndexParams = NIL;
      if(!stmt->reverse) {
        for(int i = 0; i < primaryNum; i++) {
          IndexElem *indexele = makeNode(IndexElem);
          indexele->name = colnames[i];
          newIndexParams = lappend(newIndexParams, indexele);
        }
      }
      else {
        for(int i = 0; i < dstNum; i++) {
          IndexElem *indexele = makeNode(IndexElem);
          indexele->name = colnames[srcNum + i];
          newIndexParams = lappend(newIndexParams, indexele);
        }
        for(int i = 0; i < srcNum; i++) {
          IndexElem *indexele = makeNode(IndexElem);
          indexele->name = colnames[i];
          newIndexParams = lappend(newIndexParams, indexele);
        }
      }
    stmt->indexParams = list_concat(newIndexParams, stmt->indexParams);
    }
    else {
      Relation vlabelAttRelation = heap_open(VlabelAttrRelationId, RowExclusiveLock);
      cqContext cqc;
      int vattnum = caql_getcount(
              NULL,
              cql("SELECT COUNT(*) FROM skylon_vlabel_attribute "
                " WHERE vlabelname = :1 AND schemaname = :2 "
                  "AND rank > :3",
                  CStringGetDatum(ele), CStringGetDatum(stmt->relation->schemaname), Int32GetDatum(0)));
      char **attnames = palloc0(vattnum * sizeof(char*));
      cqContext *pcqCtx = caql_beginscan(
          caql_addrel(cqclr(&cqc), vlabelAttRelation),
                        cql("SELECT * FROM skylon_vlabel_attribute "
                          " WHERE vlabelname = :1 AND schemaname = :2 "
                            "AND rank > :3",
                            CStringGetDatum(ele), CStringGetDatum(stmt->relation->schemaname), Int32GetDatum(0)));
      HeapTuple attributeTuple;
      while (HeapTupleIsValid(attributeTuple = caql_getnext(pcqCtx))) {
        Form_skylon_vlabel_attribute att = (Form_skylon_vlabel_attribute) GETSTRUCT(attributeTuple);
        attnames[att->rank - 1] = pstrdup(NameStr(att->attrname));
      }
      caql_endscan(pcqCtx);
      heap_close(vlabelAttRelation, RowExclusiveLock);
      ListCell *cell;
      foreach(cell, stmt->indexParams) {
        IndexElem *ele = (IndexElem *)lfirst(cell);
        for(int i = 0; i < vattnum; i++) {
          if(strcmp(attnames[i], ele->name) == 0) {
            Value *attnum = makeInteger(0);
            attnum->val.ival = i + 1;
            stmt->graphIndexAttnum = lappend(stmt->graphIndexAttnum, attnum);
            break;
          }
        }
      }
      foreach(cell, stmt->indexIncludingParams) {
        IndexElem *ele = (IndexElem *)lfirst(cell);
        for(int i = 0; i < vattnum; i++) {
          if(strcmp(attnames[i], ele->name) == 0) {
            Value *attnum = makeInteger(0);
            attnum->val.ival = i + 1;
            stmt->graphIncludeAttnum = lappend(stmt->graphIncludeAttnum, attnum);
            break;
          }
        }
      }
    }

  }

  /*
   * If the table already exists (i.e., this isn't a create table time
   * expansion of primary key() or unique()) and we're the ultimate parent
   * of a partitioned table, cascade to all children. We don't do this
   * at create table time because transformPartitionBy() automatically
   * creates the indexes on the child tables for us.
   *
   * If this is a CREATE INDEX statement, idxname should already exist.
   */
  idxOid = RangeVarGetRelid(stmt->relation, true, false /*allowHcatalog*/);
  nspOid = RangeVarGetCreationNamespace(stmt->relation);
  if (OidIsValid(idxOid) && stmt->idxname) {
    Relation rel;

    PG_TRY();
    { rel = heap_openrv(stmt->relation, AccessShareLock); }
    PG_CATCH();
    {
      /*
       * In the case of the table being dropped concurrently,
       * throw a friendlier error than:
       *
       * "could not open relation with relid 1234"
       */
      if (stmt->relation->schemaname)
        ereport(ERROR,
                (errcode(ERRCODE_UNDEFINED_TABLE),
                 errmsg("relation \"%s.%s\" does not exist",
                        stmt->relation->schemaname, stmt->relation->relname),
                 errOmitLocation(true)));
      else
        ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE),
                        errmsg("relation \"%s\" does not exist",
                               stmt->relation->relname),
                        errOmitLocation(true)));
      PG_RE_THROW();
    }
    PG_END_TRY();

    if (RelationBuildPartitionDesc(rel, false)) stmt->do_part = true;

    /* native orc can't create index in parent relation */
    if (RelationIsOrc(rel) && stmt->do_part)
  		ereport(ERROR, (errcode(ERRCODE_CDB_FEATURE_NOT_YET),
  				errmsg("Cannot support create index statement in native orc parent relation yet")));

    if (stmt->do_part && Gp_role != GP_ROLE_EXECUTE) {
      List *children;
      struct HTAB *nameCache;

      /* Lookup the parser object name cache */
      nameCache = parser_get_namecache(pstate);

      /* Loop over all partition children */
      children = find_inheritance_children(RelationGetRelid(rel));

      foreach (l, children) {
        Oid relid = lfirst_oid(l);
        Relation crel = heap_open(
            relid,
            NoLock); /* lock on master
                                                                    is enough */
        IndexStmt *chidx;
        Relation partrel;
        HeapTuple tuple;
        cqContext cqc;
        char *parname;
        int2 position;
        int4 depth;
        NameData name;
        Oid paroid;
        char depthstr[NAMEDATALEN];
        char prtstr[NAMEDATALEN];

        chidx = (IndexStmt *)copyObject((Node *)stmt);

        /* now just update the relation and index name fields */
        chidx->relation =
            makeRangeVar(NULL /*catalogname*/,
                         get_namespace_name(RelationGetNamespace(crel)),
                         pstrdup(RelationGetRelationName(crel)), -1);

        elog(NOTICE, "building index for child partition \"%s\"",
             RelationGetRelationName(crel));
        /*
         * We want the index name to resemble our partition table name
         * with the master index name on the front. This means, we
         * append to the indexname the parname, position, and depth
         * as we do in transformPartitionBy().
         *
         * So, firstly we must retrieve from pg_partition_rule the
         * partition descriptor for the current relid. This gives us
         * partition name and position. With paroid, we can get the
         * partition level descriptor from pg_partition and therefore
         * our depth.
         */
        partrel = heap_open(PartitionRuleRelationId, AccessShareLock);

        tuple = caql_getfirst(caql_addrel(cqclr(&cqc), partrel),
                              cql("SELECT * FROM pg_partition_rule "
                                  " WHERE parchildrelid = :1 ",
                                  ObjectIdGetDatum(relid)));

        Assert(HeapTupleIsValid(tuple));

        name = ((Form_pg_partition_rule)GETSTRUCT(tuple))->parname;
        parname = pstrdup(NameStr(name));
        position = ((Form_pg_partition_rule)GETSTRUCT(tuple))->parruleord;
        paroid = ((Form_pg_partition_rule)GETSTRUCT(tuple))->paroid;

        heap_freetuple(tuple);
        heap_close(partrel, NoLock);

        partrel = heap_open(PartitionRelationId, AccessShareLock);

        tuple = caql_getfirst(caql_addrel(cqclr(&cqc), partrel),
                              cql("SELECT parlevel FROM pg_partition "
                                  " WHERE oid = :1 ",
                                  ObjectIdGetDatum(paroid)));

        Assert(HeapTupleIsValid(tuple));

        depth = ((Form_pg_partition)GETSTRUCT(tuple))->parlevel + 1;

        heap_freetuple(tuple);
        heap_close(partrel, NoLock);

        heap_close(crel, NoLock);

        /* now, build the piece to append */
        snprintf(depthstr, sizeof(depthstr), "%d", depth);
        if (strlen(parname) == 0)
          snprintf(prtstr, sizeof(prtstr), "prt_%d", position);
        else
          snprintf(prtstr, sizeof(prtstr), "prt_%s", parname);

        chidx->idxname = ChooseRelationName(stmt->idxname, depthstr, /* depth */
                                            prtstr, /* part spec */
                                            nspOid, nameCache);

        *extras_after = lappend(*extras_after, chidx);
      }
    }

    heap_close(rel, AccessShareLock);
  }

  /* take care of the where clause */
  if (stmt->whereClause) {
    /*
     * Put the parent table into the rtable so that the WHERE clause can
     * refer to its fields without qualification.  Note that this only
     * works if the parent table already exists --- so we can't easily
     * support predicates on indexes created implicitly by CREATE TABLE.
     * Fortunately, that's not necessary.
     */
    rte = addRangeTableEntry(pstate, stmt->relation, NULL, false, true);

    /* no to join list, yes to namespaces */
    addRTEtoQuery(pstate, rte, false, true, true);

    stmt->whereClause =
        transformWhereClause(pstate, stmt->whereClause, "WHERE");
  }

  /* take care of any index expressions */
  foreach (l, stmt->indexParams) {
    IndexElem *ielem = (IndexElem *)lfirst(l);

    if (ielem->expr) {
      /* Set up rtable as for predicate, see notes above */
      if (rte == NULL) {
        rte = addRangeTableEntry(pstate, stmt->relation, NULL, false, true);
        /* no to join list, yes to namespaces */
        addRTEtoQuery(pstate, rte, false, true, true);
      }
      ielem->expr = transformExpr(pstate, ielem->expr);

      /*
       * We check only that the result type is legitimate; this is for
       * consistency with what transformWhereClause() checks for the
       * predicate.  DefineIndex() will make more checks.
       */
      if (expression_returns_set(ielem->expr))
        ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH),
                        errmsg("index expression may not return a set"),
                        errOmitLocation(true)));
    }
  }

  qry->hasSubLinks = pstate->p_hasSubLinks;
  stmt->rangetable = pstate->p_rtable;

  qry->utilityStmt = (Node *)stmt;

  return qry;
}

/*
 * transformRuleStmt -
 *	  transform a Create Rule Statement. The actions is a list of parse
 *	  trees which is transformed into a list of query trees.
 */
static Query *transformRuleStmt(ParseState *pstate, RuleStmt *stmt,
                                List **extras_before, List **extras_after) {
  Query *qry;
  Relation rel;
  RangeTblEntry *oldrte;
  RangeTblEntry *newrte;

  qry = makeNode(Query);
  qry->commandType = CMD_UTILITY;
  qry->utilityStmt = (Node *)stmt;

  /*
   * To avoid deadlock, make sure the first thing we do is grab
   * AccessExclusiveLock on the target relation.	This will be needed by
   * DefineQueryRewrite(), and we don't want to grab a lesser lock
   * beforehand.
   */
  rel = heap_openrv(stmt->relation, AccessExclusiveLock);

  /*
   * NOTE: 'OLD' must always have a varno equal to 1 and 'NEW' equal to 2.
   * Set up their RTEs in the main pstate for use in parsing the rule
   * qualification.
   */
  Assert(pstate->p_rtable == NIL);
  oldrte = addRangeTableEntryForRelation(pstate, rel, makeAlias("*OLD*", NIL),
                                         false, false);
  newrte = addRangeTableEntryForRelation(pstate, rel, makeAlias("*NEW*", NIL),
                                         false, false);
  /* Must override addRangeTableEntry's default access-check flags */
  oldrte->requiredPerms = 0;
  newrte->requiredPerms = 0;

  /*
   * They must be in the namespace too for lookup purposes, but only add the
   * one(s) that are relevant for the current kind of rule.  In an UPDATE
   * rule, quals must refer to OLD.field or NEW.field to be unambiguous, but
   * there's no need to be so picky for INSERT & DELETE.  We do not add them
   * to the joinlist.
   */
  switch (stmt->event) {
    case CMD_SELECT:
      addRTEtoQuery(pstate, oldrte, false, true, true);
      break;
    case CMD_UPDATE:
      addRTEtoQuery(pstate, oldrte, false, true, true);
      addRTEtoQuery(pstate, newrte, false, true, true);
      break;
    case CMD_INSERT:
      addRTEtoQuery(pstate, newrte, false, true, true);
      break;
    case CMD_DELETE:
      addRTEtoQuery(pstate, oldrte, false, true, true);
      break;
    default:
      elog(ERROR, "unrecognized event type: %d", (int)stmt->event);
      break;
  }

  /* take care of the where clause */
  stmt->whereClause = transformWhereClause(pstate, stmt->whereClause, "WHERE");

  /* CDB: Cursor position not available for errors below this point. */
  pstate->p_breadcrumb.node = NULL;

  if (list_length(pstate->p_rtable) != 2) /* naughty, naughty... */
    ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
                    errmsg(
                        "rule WHERE condition may not contain references to "
                        "other relations"),
                    errOmitLocation(true)));

  /* aggregates not allowed (but subselects are okay) */
  if (pstate->p_hasAggs)
    ereport(ERROR,
            (errcode(ERRCODE_GROUPING_ERROR),
             errmsg("cannot use aggregate function in rule WHERE condition"),
             errOmitLocation(true)));

  if (pstate->p_hasWindFuncs)
    ereport(ERROR,
            (errcode(ERRCODE_SYNTAX_ERROR),
             errmsg("cannot use window function in rule WHERE condition"),
             errOmitLocation(true)));

  /* save info about sublinks in where clause */
  qry->hasSubLinks = pstate->p_hasSubLinks;

  /*
   * 'instead nothing' rules with a qualification need a query rangetable so
   * the rewrite handler can add the negated rule qualification to the
   * original query. We create a query with the new command type CMD_NOTHING
   * here that is treated specially by the rewrite system.
   */
  if (stmt->actions == NIL) {
    Query *nothing_qry = makeNode(Query);

    nothing_qry->commandType = CMD_NOTHING;
    nothing_qry->rtable = pstate->p_rtable;
    nothing_qry->jointree = makeFromExpr(NIL, NULL); /* no join wanted */

    stmt->actions = list_make1(nothing_qry);
  } else {
    ListCell *l;
    List *newactions = NIL;

    /*
     * transform each statement, like parse_sub_analyze()
     */
    foreach (l, stmt->actions) {
      Node *action = (Node *)lfirst(l);
      ParseState *sub_pstate = make_parsestate(pstate->parentParseState);
      Query *sub_qry, *top_subqry;
      bool has_old, has_new;

      /*
       * Set up OLD/NEW in the rtable for this statement.  The entries
       * are added only to relnamespace, not varnamespace, because we
       * don't want them to be referred to by unqualified field names
       * nor "*" in the rule actions.  We decide later whether to put
       * them in the joinlist.
       */
      oldrte = addRangeTableEntryForRelation(
          sub_pstate, rel, makeAlias("*OLD*", NIL), false, false);
      newrte = addRangeTableEntryForRelation(
          sub_pstate, rel, makeAlias("*NEW*", NIL), false, false);
      oldrte->requiredPerms = 0;
      newrte->requiredPerms = 0;
      addRTEtoQuery(sub_pstate, oldrte, false, true, false);
      addRTEtoQuery(sub_pstate, newrte, false, true, false);

      /* Transform the rule action statement */
      top_subqry =
          transformStmt(sub_pstate, action, extras_before, extras_after);

      /*
       * We cannot support utility-statement actions (eg NOTIFY) with
       * nonempty rule WHERE conditions, because there's no way to make
       * the utility action execute conditionally.
       */
      if (top_subqry->commandType == CMD_UTILITY && stmt->whereClause != NULL)
        ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
                        errmsg(
                            "rules with WHERE conditions may only have SELECT, "
                            "INSERT, UPDATE, or DELETE actions"),
                        errOmitLocation(true)));

      /*
       * If the action is INSERT...SELECT, OLD/NEW have been pushed down
       * into the SELECT, and that's what we need to look at. (Ugly
       * kluge ... try to fix this when we redesign querytrees.)
       */
      sub_qry = getInsertSelectQuery(top_subqry, NULL);

      /*
       * If the sub_qry is a setop, we cannot attach any qualifications
       * to it, because the planner won't notice them.  This could
       * perhaps be relaxed someday, but for now, we may as well reject
       * such a rule immediately.
       */
      if (sub_qry->setOperations != NULL && stmt->whereClause != NULL)
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        errmsg(
                            "conditional UNION/INTERSECT/EXCEPT statements are "
                            "not implemented"),
                        errOmitLocation(true)));

      /*
       * Validate action's use of OLD/NEW, qual too
       */
      has_old = rangeTableEntry_used((Node *)sub_qry, PRS2_OLD_VARNO, 0) ||
                rangeTableEntry_used(stmt->whereClause, PRS2_OLD_VARNO, 0);
      has_new = rangeTableEntry_used((Node *)sub_qry, PRS2_NEW_VARNO, 0) ||
                rangeTableEntry_used(stmt->whereClause, PRS2_NEW_VARNO, 0);

      switch (stmt->event) {
        case CMD_SELECT:
          if (has_old)
            ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
                            errmsg("ON SELECT rule may not use OLD"),
                            errOmitLocation(true)));
          if (has_new)
            ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
                            errmsg("ON SELECT rule may not use NEW"),
                            errOmitLocation(true)));
          break;
        case CMD_UPDATE:
          /* both are OK */
          break;
        case CMD_INSERT:
          if (has_old)
            ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
                            errmsg("ON INSERT rule may not use OLD"),
                            errOmitLocation(true)));
          break;
        case CMD_DELETE:
          if (has_new)
            ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
                            errmsg("ON DELETE rule may not use NEW"),
                            errOmitLocation(true)));
          break;
        default:
          elog(ERROR, "unrecognized event type: %d", (int)stmt->event);
          break;
      }

      /*
       * For efficiency's sake, add OLD to the rule action's jointree
       * only if it was actually referenced in the statement or qual.
       *
       * For INSERT, NEW is not really a relation (only a reference to
       * the to-be-inserted tuple) and should never be added to the
       * jointree.
       *
       * For UPDATE, we treat NEW as being another kind of reference to
       * OLD, because it represents references to *transformed* tuples
       * of the existing relation.  It would be wrong to enter NEW
       * separately in the jointree, since that would cause a double
       * join of the updated relation.  It's also wrong to fail to make
       * a jointree entry if only NEW and not OLD is mentioned.
       */
      if (has_old || (has_new && stmt->event == CMD_UPDATE)) {
        /*
         * If sub_qry is a setop, manipulating its jointree will do no
         * good at all, because the jointree is dummy. (This should be
         * a can't-happen case because of prior tests.)
         */
        if (sub_qry->setOperations != NULL)
          ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                          errmsg(
                              "conditional UNION/INTERSECT/EXCEPT statements "
                              "are not implemented"),
                          errOmitLocation(true)));
        /* hack so we can use addRTEtoQuery() */
        sub_pstate->p_rtable = sub_qry->rtable;
        sub_pstate->p_joinlist = sub_qry->jointree->fromlist;
        addRTEtoQuery(sub_pstate, oldrte, true, false, false);
        sub_qry->jointree->fromlist = sub_pstate->p_joinlist;
      }

      newactions = lappend(newactions, top_subqry);

      release_pstate_resources(sub_pstate);
      free_parsestate(&sub_pstate);
    }

    stmt->actions = newactions;
  }

  /* Close relation, but keep the exclusive lock */
  heap_close(rel, NoLock);

  return qry;
}

/*
 * If an input query (Q) mixes window functions with aggregate
 * functions or grouping, then (per SQL:2003) we need to divide
 * it into an outer query, Q', that contains no aggregate calls
 * or grouping and an inner query, Q'', that contains no window
 * calls.
 *
 * Q' will have a 1-entry range table whose entry corresponds to
 * the results of Q''.
 *
 * Q'' will have the same range as Q and will be pushed down into
 * a subquery range table entry in Q'.
 *
 * As a result, the depth of outer references in Q'' and below
 * will increase, so we need to adjust non-zero xxxlevelsup fields
 * (Var, Aggref, and WindowRef nodes) in Q'' and below.  At the end,
 * there will be no levelsup items referring to Q'.  Prior references
 * to Q will now refer to Q''; prior references to blocks above Q will
 * refer to the same blocks above Q'.)
 *
 * We do all this by creating a new Query node, subq, for Q''.  We
 * modify the input Query node, qry, in place for Q'.  (Since qry is
 * also the input, Q, be careful not to destroy values before we're
 * done with them.
 */
static Query *transformGroupedWindows(Query *qry) {
  Query *subq;
  RangeTblEntry *rte;
  RangeTblRef *ref;
  Alias *alias;
  bool hadSubLinks = qry->hasSubLinks;

  grouped_window_ctx ctx;

  Assert(qry->commandType == CMD_SELECT);
  Assert(!PointerIsValid(qry->utilityStmt));
  Assert(qry->returningList == NIL);

  if (!qry->hasWindFuncs || !(qry->groupClause || qry->hasAggs)) return qry;

  /* Make the new subquery (Q'').  Note that (per SQL:2003) there
   * can't be any window functions called in the WHERE, GROUP BY,
   * or HAVING clauses.
   */
  subq = makeNode(Query);
  subq->commandType = CMD_SELECT;
  subq->querySource = QSRC_PARSER;
  subq->canSetTag = true;
  subq->utilityStmt = NULL;
  subq->resultRelation = 0;
  subq->intoClause = NULL;
  subq->hasAggs = qry->hasAggs;
  subq->hasWindFuncs = false;           /* reevaluate later */
  subq->hasSubLinks = qry->hasSubLinks; /* reevaluate later */

  /* Core of subquery input table expression: */
  subq->rtable = qry->rtable;     /* before windowing */
  subq->jointree = qry->jointree; /* before windowing */
  subq->targetList = NIL;         /* fill in later */

  subq->returningList = NIL;
  subq->groupClause = qry->groupClause; /* before windowing */
  subq->havingQual = qry->havingQual;   /* before windowing */
  subq->windowClause = NIL;             /* by construction */
  subq->distinctClause = NIL;           /* after windowing */
  subq->sortClause = NIL;               /* after windowing */
  subq->limitOffset = NULL;             /* after windowing */
  subq->limitCount = NULL;              /* after windowing */
  subq->rowMarks = NIL;
  subq->setOperations = NULL;

  /* Check if there is a window function in the join tree. If so
   * we must mark hasWindFuncs in the sub query as well.
   */
  if (checkExprHasWindFuncs((Node *)subq->jointree)) subq->hasWindFuncs = true;

  /* Make the single range table entry for the outer query Q' as
   * a wrapper for the subquery (Q'') currently under construction.
   */
  rte = makeNode(RangeTblEntry);
  rte->rtekind = RTE_SUBQUERY;
  rte->subquery = subq;
  rte->alias = NULL; /* fill in later */
  rte->eref = NULL;  /* fill in later */
  rte->inFromCl = true;
  rte->requiredPerms = ACL_SELECT;
  /* Default?
   * rte->inh = 0;
   * rte->checkAsUser = 0;
   * rte->pseudocols = 0;
  */

  /* Make a reference to the new range table entry .
   */
  ref = makeNode(RangeTblRef);
  ref->rtindex = 1;

  /* Set up context for mutating the target list.  Careful.
   * This is trickier than it looks.  The context will be
   * "primed" with grouping targets.
   */
  init_grouped_window_context(&ctx, qry);

  /* Begin rewriting the outer query in place.
   */
  qry->hasAggs = false; /* by constuction */
  /* qry->hasSubLinks -- reevaluate later. */

  /* Core of outer query input table expression: */
  qry->rtable = list_make1(rte);
  qry->jointree = (FromExpr *)makeNode(FromExpr);
  qry->jointree->fromlist = list_make1(ref);
  qry->jointree->quals = NULL;
  /* qry->targetList -- to be mutated from Q to Q' below */

  qry->groupClause = NIL; /* by construction */
  qry->havingQual = NULL; /* by construction */

  /* Mutate the Q target list and windowClauses for use in Q' and, at the
   * same time, update state with info needed to assemble the target list
   * for the subquery (Q'').
   */
  qry->targetList =
      (List *)grouped_window_mutator((Node *)qry->targetList, &ctx);
  qry->windowClause =
      (List *)grouped_window_mutator((Node *)qry->windowClause, &ctx);
  qry->hasSubLinks = checkExprHasSubLink((Node *)qry->targetList);

  /* New subquery fields
   */
  subq->targetList = ctx.subtlist;
  subq->groupClause = ctx.subgroupClause;

  /* We always need an eref, but we shouldn't really need a filled in alias.
   * However, view deparse (or at least the fix for MPP-2189) wants one.
   */
  alias = make_replacement_alias(subq, "Window");
  rte->eref = copyObject(alias);
  rte->alias = alias;

  /* Accomodate depth change in new subquery, Q''.
   */
  IncrementVarSublevelsUpInTransformGroupedWindows((Node *)subq, 1, 1);

  /* Might have changed. */
  subq->hasSubLinks = checkExprHasSubLink((Node *)subq);

  Assert(PointerIsValid(qry->targetList));
  Assert(IsA(qry->targetList, List));
  /* Use error instead of assertion to "use" hadSubLinks and keep compiler
   * happy. */
  if (hadSubLinks != (qry->hasSubLinks || subq->hasSubLinks))
    elog(ERROR,
         "inconsistency detected in internal grouped windows transformation");

  discard_grouped_window_context(&ctx);

  return qry;
}

/* Helper for transformGroupedWindows:
 *
 * Prime the subquery target list in the context with the grouping
 * and windowing attributes from the given query and adjust the
 * subquery group clauses in the context to agree.
 *
 * Note that we arrange dense sortgroupref values and stash the
 * referents on the front of the subquery target list.  This may
 * be over-kill, but the grouping extension code seems to like it
 * this way.
 *
 * Note that we only transfer sortgrpref values associated with
 * grouping and windowing to the subquery context.  The subquery
 * shouldn't care about ordering, etc. XXX
 */
static void init_grouped_window_context(grouped_window_ctx *ctx, Query *qry) {
  List *grp_tles = NIL;
  ListCell *lc = NULL;
  Index maxsgr = 0;

  grp_tles = get_sortgroupclauses_tles(qry->groupClause, qry->targetList);
  maxsgr = maxSortGroupRef(grp_tles, true);

  ctx->subtlist = NIL;
  ctx->subgroupClause = NIL;

  /* Set up scratch space.
   */

  ctx->subrtable = qry->rtable;

  /* Map input = outer query sortgroupref values to subquery values while
   * building the
   * subquery target list prefix. */
  ctx->sgr_map = palloc0((maxsgr + 1) * sizeof(ctx->sgr_map[0]));
  foreach (lc, grp_tles) {
    TargetEntry *tle;
    Index old_sgr;

    tle = (TargetEntry *)copyObject(lfirst(lc));
    old_sgr = tle->ressortgroupref;

    ctx->subtlist = lappend(ctx->subtlist, tle);
    tle->resno = list_length(ctx->subtlist);
    tle->ressortgroupref = tle->resno;
    tle->resjunk = false;

    ctx->sgr_map[old_sgr] = tle->ressortgroupref;
  }

  /* Miscellaneous scratch area. */
  ctx->call_depth = 0;
  ctx->tle = NULL;

  /* Revise grouping into ctx->subgroupClause */
  ctx->subgroupClause = (List *)map_sgr_mutator((Node *)qry->groupClause, ctx);
}

/* Helper for transformGroupedWindows */
static void discard_grouped_window_context(grouped_window_ctx *ctx) {
  ctx->subtlist = NIL;
  ctx->subgroupClause = NIL;
  ctx->tle = NULL;
  if (ctx->sgr_map) pfree(ctx->sgr_map);
  ctx->sgr_map = NULL;
  ctx->subrtable = NULL;
}

/* Helper for transformGroupedWindows:
 *
 * Look for the given expression in the context's subtlist.  If
 * none is found and the force argument is true, add a target
 * for it.  Make and return a variable referring to the target
 * with the matching expression, or return NULL, if no target
 * was found/added.
 */
static Var *var_for_gw_expr(grouped_window_ctx *ctx, Node *expr, bool force) {
  Var *var = NULL;
  TargetEntry *tle = tlist_member(expr, ctx->subtlist);

  if (tle == NULL && force) {
    tle = makeNode(TargetEntry);
    ctx->subtlist = lappend(ctx->subtlist, tle);
    tle->expr = (Expr *)expr;
    tle->resno = list_length(ctx->subtlist);
    /* See comment in grouped_window_mutator for why level 3 is appropriate. */
    if (ctx->call_depth == 3 && ctx->tle != NULL && ctx->tle->resname != NULL) {
      tle->resname = pstrdup(ctx->tle->resname);
    } else {
      tle->resname = generate_positional_name(tle->resno);
    }
    tle->ressortgroupref = 0;
    tle->resorigtbl = 0;
    tle->resorigcol = 0;
    tle->resjunk = false;
  }

  if (tle != NULL) {
    var = makeNode(Var);
    var->varno = 1;             /* one and only */
    var->varattno = tle->resno; /* by construction */
    var->vartype = exprType((Node *)tle->expr);
    var->vartypmod = exprTypmod((Node *)tle->expr);
    var->varlevelsup = 0;
    var->varnoold = 1;
    var->varoattno = tle->resno;
    var->location = 0;
  }

  return var;
}

/* Helper for transformGroupedWindows:
 *
 * Mutator for subquery groupingClause to adjust sortgrpref values
 * based on map developed while priming context target list.
 */
static Node *map_sgr_mutator(Node *node, void *context) {
  grouped_window_ctx *ctx = (grouped_window_ctx *)context;

  if (!node) return NULL;

  if (IsA(node, List)) {
    ListCell *lc;
    List *new_lst = NIL;

    foreach (lc, (List *)node) {
      Node *newnode = lfirst(lc);
      newnode = map_sgr_mutator(newnode, ctx);
      new_lst = lappend(new_lst, newnode);
    }
    return (Node *)new_lst;
  }

  else if (IsA(node, GroupClause)) {
    GroupClause *g = (GroupClause *)node;
    GroupClause *new_g = makeNode(GroupClause);
    memcpy(new_g, g, sizeof(GroupClause));
    new_g->tleSortGroupRef = ctx->sgr_map[g->tleSortGroupRef];
    return (Node *)new_g;
  }

  /* Just like above, but don't assume identical */
  else if (IsA(node, SortClause)) {
    SortClause *s = (SortClause *)node;
    SortClause *new_s = makeNode(SortClause);
    memcpy(new_s, s, sizeof(SortClause));
    new_s->tleSortGroupRef = ctx->sgr_map[s->tleSortGroupRef];
    return (Node *)new_s;
  }

  else if (IsA(node, GroupingClause)) {
    GroupingClause *gc = (GroupingClause *)node;
    GroupingClause *new_gc = makeNode(GroupingClause);
    memcpy(new_gc, gc, sizeof(GroupingClause));
    new_gc->groupsets = (List *)map_sgr_mutator((Node *)gc->groupsets, ctx);
    return (Node *)new_gc;
  }

  return NULL; /* Never happens */
}

/*
 * Helper for transformGroupedWindows:
 *
 * Transform targets from Q into targets for Q' and place information
 * needed to eventually construct the target list for the subquery Q''
 * in the context structure.
 *
 * The general idea is to add expressions that must be evaluated in the
 * subquery to the subquery target list (in the context) and to replace
 * them with Var nodes in the outer query.
 *
 * If there are any Agg nodes in the Q'' target list, arrange
 * to set hasAggs to true in the subquery. (This should already be
 * done, though).
 *
 * If we're pushing down an entire TLE that has a resname, use
 * it as an alias in the upper TLE, too.  Facilitate this by copying
 * down the resname from an immediately enclosing TargetEntry, if any.
 *
 * The algorithm repeatedly searches the subquery target list under
 * construction (quadric), however we don't expect many targets so
 * we don't optimize this.  (Could, for example, use a hash or divide
 * the target list into var, expr, and group/aggregate function lists.)
 */

static Node *grouped_window_mutator(Node *node, void *context) {
  Node *result = NULL;

  grouped_window_ctx *ctx = (grouped_window_ctx *)context;

  if (!node) return result;

  ctx->call_depth++;

  if (IsA(node, TargetEntry)) {
    TargetEntry *tle = (TargetEntry *)node;
    TargetEntry *new_tle = makeNode(TargetEntry);

    /* Copy the target entry. */
    new_tle->resno = tle->resno;
    if (tle->resname == NULL) {
      new_tle->resname = generate_positional_name(new_tle->resno);
    } else {
      new_tle->resname = pstrdup(tle->resname);
    }
    new_tle->ressortgroupref = tle->ressortgroupref;
    new_tle->resorigtbl = InvalidOid;
    new_tle->resorigcol = 0;
    new_tle->resjunk = tle->resjunk;

    /* This is pretty shady, but we know our call pattern.  The target
     * list is at level 1, so we're interested in target entries at level
     * 2.  We record them in context so var_for_gw_expr can maybe make a better
     * than default choice of alias.
     */
    if (ctx->call_depth == 2) {
      ctx->tle = tle;
    } else {
      ctx->tle = NULL;
    }

    new_tle->expr = (Expr *)grouped_window_mutator((Node *)tle->expr, ctx);

    ctx->tle = NULL;
    result = (Node *)new_tle;
  } else if (IsA(node, Aggref) || IsA(node, PercentileExpr) ||
             IsA(node, GroupingFunc) || IsA(node, GroupId)) {
    /* Aggregation expression */
    result = (Node *)var_for_gw_expr(ctx, node, true);
  } else if (IsA(node, Var)) {
    Var *var = (Var *)node;

    /* Since this is a Var (leaf node), we must be able to mutate it,
     * else we can't finish the transformation and must give up.
     */
    result = (Node *)var_for_gw_expr(ctx, node, false);

    if (!result) {
      List *altvars = generate_alternate_vars(var, ctx);
      ListCell *lc;
      foreach (lc, altvars) {
        result = (Node *)var_for_gw_expr(ctx, lfirst(lc), false);
        if (result) break;
      }
    }

    if (!result)
      ereport(ERROR, (errcode(ERRCODE_WINDOWING_ERROR),
                      errmsg("unresolved grouping key in window query"),
                      errhint(
                          "You may need to use explicit aliases and/or to "
                          "refer to grouping "
                          "keys in the same way throughout the query."),
                      errOmitLocation(true)));
  } else {
    /* Grouping expression; may not find one. */
    result = (Node *)var_for_gw_expr(ctx, node, false);
  }

  if (!result) {
    result = expression_tree_mutator(node, grouped_window_mutator, ctx);
  }

  ctx->call_depth--;
  return result;
}

/*
 * Helper for transformGroupedWindows:
 *
 * Build an Alias for a subquery RTE representing the given Query.
 * The input string aname is the name for the overall Alias. The
 * attribute names are all found or made up.
 */
static Alias *make_replacement_alias(Query *qry, const char *aname) {
  ListCell *lc = NULL;
  char *name = NULL;
  Alias *alias = makeNode(Alias);
  AttrNumber attrno = 0;

  alias->aliasname = pstrdup(aname);
  alias->colnames = NIL;

  foreach (lc, qry->targetList) {
    TargetEntry *tle = (TargetEntry *)lfirst(lc);
    attrno++;

    if (tle->resname) {
      /* Prefer the target's resname. */
      name = pstrdup(tle->resname);
    } else if (IsA(tle->expr, Var)) {
      /* If the target expression is a Var, use the name of the
       * attribute in the query's range table. */
      Var *var = (Var *)tle->expr;
      RangeTblEntry *rte = rt_fetch(var->varno, qry->rtable);
      name = pstrdup(get_rte_attribute_name(rte, var->varattno));
    } else {
      /* If all else, fails, generate a name based on position. */
      name = generate_positional_name(attrno);
    }

    alias->colnames = lappend(alias->colnames, makeString(name));
  }
  return alias;
}

/*
 * Helper for transformGroupedWindows:
 *
 * Make a palloc'd C-string named for the input attribute number.
 */
static char *generate_positional_name(AttrNumber attrno) {
  int rc = 0;
  char buf[NAMEDATALEN];

  rc = snprintf(buf, sizeof(buf), "att_%d", attrno);
  if (rc == EOF || rc < 0 || rc >= sizeof(buf)) {
    ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR),
                    errmsg("can't generate internal attribute name"),
                    errOmitLocation(true)));
  }
  return pstrdup(buf);
}

/*
 * Helper for transformGroupedWindows:
 *
 * Find alternate Vars on the range of the input query that are aliases
 * (modulo ANSI join) of the input Var on the range and that occur in the
 * target list of the input query.
 *
 * If the input Var references a join result, there will be a single
 * alias.  If not, we need to search the range table for occurances
 * of the input Var in some join result's RTE and add a Var referring
 * to the appropriate attribute of the join RTE to the list.
 *
 * This is not efficient, but the need is rare (MPP-12082) so we don't
 * bother to precompute this.
 */
static List *generate_alternate_vars(Var *invar, grouped_window_ctx *ctx) {
  List *rtable = ctx->subrtable;
  RangeTblEntry *inrte;
  List *alternates = NIL;

  Assert(IsA(invar, Var));

  inrte = rt_fetch(invar->varno, rtable);

  if (inrte->rtekind == RTE_JOIN) {
    Node *ja = list_nth(inrte->joinaliasvars, invar->varattno - 1);

    /* Though Node types other than Var (e.g., CoalesceExpr or Const) may occur
     * as joinaliasvars, we ignore them.
     */
    if (IsA(ja, Var)) {
      alternates = lappend(alternates, copyObject(ja));
    }
  } else {
    ListCell *jlc;
    Index varno = 0;

    foreach (jlc, rtable) {
      RangeTblEntry *rte = (RangeTblEntry *)lfirst(jlc);

      varno++; /* This RTE's varno */

      if (rte->rtekind == RTE_JOIN) {
        ListCell *alc;
        AttrNumber attno = 0;

        foreach (alc, rte->joinaliasvars) {
          ListCell *tlc;
          Node *altnode = lfirst(alc);
          Var *altvar = (Var *)altnode;

          attno++; /* This attribute's attno in its join RTE */

          if (!IsA(altvar, Var) || !equal(invar, altvar)) continue;

          /* Look for a matching Var in the target list. */

          foreach (tlc, ctx->subtlist) {
            TargetEntry *tle = (TargetEntry *)lfirst(tlc);
            Var *v = (Var *)tle->expr;

            if (IsA(v, Var) && v->varno == varno && v->varattno == attno) {
              alternates = lappend(alternates, tle->expr);
            }
          }
        }
      }
    }
  }
  return alternates;
}

/*
 * transformSelectStmt -
 *	  transforms a Select Statement
 *
 * Note: this is also used for DECLARE CURSOR statements.
 */
static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt) {
  Query *qry = makeNode(Query);
  Node *qual;
  ListCell *l;

  qry->commandType = CMD_SELECT;

  /* setup database name for use of magma operations */
  MemoryContext oldContext = MemoryContextSwitchTo(MessageContext);
  database = get_database_name(MyDatabaseId);
  MemoryContextSwitchTo(oldContext);

  /* process the WITH clause */
  if (stmt->withClause != NULL) {
    qry->hasRecursive = stmt->withClause->recursive;
    qry->cteList = transformWithClause(pstate, stmt->withClause);
    qry->hasModifyingCTE = pstate->p_hasModifyingCTE;
  }

  /* make FOR UPDATE/FOR SHARE info available to addRangeTableEntry */
  pstate->p_locking_clause = stmt->lockingClause;

  /*
   * Put WINDOW clause data into pstate so that window references know
   * about them.
   */
  pstate->p_win_clauses = stmt->windowClause;

  /* process the FROM clause */
  transformFromClause(pstate, stmt->fromClause);

  /* tidy up expressions in window clauses */
  transformWindowSpecExprs(pstate);

  /* transform targetlist */
  qry->targetList = transformTargetList(pstate, stmt->targetList);

  /* mark column origins */
  markTargetListOrigins(pstate, qry->targetList);

  /* transform WHERE */
  qual = transformWhereClause(pstate, stmt->whereClause, "WHERE");

  /*
   * Initial processing of HAVING clause is just like WHERE clause.
   */
  pstate->having_qual =
      transformWhereClause(pstate, stmt->havingClause, "HAVING");

  /*
   * CDB: Untyped Const or Param nodes in a subquery in the FROM clause
   * might have been assigned proper types when we transformed the WHERE
   * clause, targetlist, etc.  Bring targetlist Var types up to date.
   */
  fixup_unknown_vars_in_targetlist(pstate, qry->targetList);

  /*
   * Transform sorting/grouping stuff.  Do ORDER BY first because both
   * transformGroupClause and transformDistinctClause need the results.
   */
  qry->sortClause = transformSortClause(
      pstate, stmt->sortClause, &qry->targetList, true, /* fix unknowns */
      false /* use SQL92 rules */);

  qry->groupClause =
      transformGroupClause(pstate, stmt->groupClause, &qry->targetList,
                           qry->sortClause, false /* useSQL92 rules */);

  /*
   * SCATTER BY clause on a table function TableValueExpr subquery.
   *
   * Note: a given subquery cannot have both a SCATTER clause and an INTO
   * clause, because both of those control distribution.  This should not
   * possible due to grammar restrictions on where a SCATTER clause is
   * allowed.
   */
  Insist(!(stmt->scatterClause && stmt->intoClause));
  qry->scatterClause =
      transformScatterClause(pstate, stmt->scatterClause, &qry->targetList);

  /* Having clause */
  qry->havingQual = pstate->having_qual;
  pstate->having_qual = NULL;

  /*
   * Process WINDOW clause.
   */
  transformWindowClause(pstate, qry);

  qry->distinctClause =
      transformDistinctClause(pstate, stmt->distinctClause, &qry->targetList,
                              &qry->sortClause, &qry->groupClause);

  qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset, "OFFSET");
  qry->limitCount = transformLimitClause(pstate, stmt->limitCount, "LIMIT");

  /* CDB: Cursor position not available for errors below this point. */
  pstate->p_breadcrumb.node = NULL;

  /* handle any SELECT INTO/CREATE TABLE AS spec */
  qry->intoClause = NULL;
  if (stmt->intoClause) {
    qry->intoClause = stmt->intoClause;
    if (stmt->intoClause->colNames)
      applyColumnNames(qry->targetList, stmt->intoClause->colNames);
    /* XXX XXX:		qry->partitionBy = stmt->partitionBy; */
  }

  /*
   * Generally, we'll only have a distributedBy clause if stmt->into is set,
   * with the exception of set op queries, since transformSetOperationStmt()
   * sets stmt->into to NULL to avoid complications elsewhere.
   */
  if (Gp_role == GP_ROLE_DISPATCH) setQryDistributionPolicy(stmt, qry);

  qry->rtable = pstate->p_rtable;
  qry->jointree = makeFromExpr(pstate->p_joinlist, qual);

  qry->hasSubLinks = pstate->p_hasSubLinks;
  qry->hasAggs = pstate->p_hasAggs;
  if (pstate->p_hasAggs || qry->groupClause || qry->havingQual)
    parseCheckAggregates(pstate, qry);

  if (pstate->p_hasTblValueExpr) parseCheckTableFunctions(pstate, qry);

  qry->hasWindFuncs = pstate->p_hasWindFuncs;
  if (pstate->p_hasWindFuncs) parseProcessWindFuncs(pstate, qry);

  foreach (l, stmt->lockingClause) {
    /* disable select for update/share for gpsql */
    ereport(ERROR,
            (errcode(ERRCODE_CDB_FEATURE_NOT_YET),
             errmsg("Cannot support select for update/share statement yet")));
    /*transformLockingClause(qry, (LockingClause *) lfirst(l));*/
  }

  /*
   * If the query mixes window functions and aggregates, we need to
   * transform it such that the grouped query appears as a subquery
   */
  if (qry->hasWindFuncs && (qry->groupClause || qry->hasAggs))
    transformGroupedWindows(qry);

  return qry;
}

/*
 * transformValuesClause -
 *	  transforms a VALUES clause that's being used as a standalone SELECT
 *
 * We build a Query containing a VALUES RTE, rather as if one had written
 *			SELECT * FROM (VALUES ...)
 */
static Query *transformValuesClause(ParseState *pstate, SelectStmt *stmt) {
  Query *qry = makeNode(Query);
  List *exprsLists = NIL;
  List **coltype_lists = NULL;
  Oid *coltypes = NULL;
  int sublist_length = -1;
  List *newExprsLists;
  RangeTblEntry *rte;
  RangeTblRef *rtr;
  ListCell *lc;
  ListCell *lc2;
  int i;

  qry->commandType = CMD_SELECT;

  /* Most SELECT stuff doesn't apply in a VALUES clause */
  Assert(stmt->distinctClause == NIL);
  Assert(stmt->targetList == NIL);
  Assert(stmt->fromClause == NIL);
  Assert(stmt->whereClause == NULL);
  Assert(stmt->groupClause == NIL);
  Assert(stmt->havingClause == NULL);
  Assert(stmt->scatterClause == NIL);
  Assert(stmt->op == SETOP_NONE);

  /* process the WITH clause independently of all else */
  if (stmt->withClause != NULL) {
    qry->hasRecursive = stmt->withClause->recursive;
    qry->cteList = transformWithClause(pstate, stmt->withClause);
    qry->hasModifyingCTE = pstate->p_hasModifyingCTE;
  }

  /*
   * For each row of VALUES, transform the raw expressions and gather type
   * information.  This is also a handy place to reject DEFAULT nodes, which
   * the grammar allows for simplicity.
   */
  foreach (lc, stmt->valuesLists) {
    List *sublist = (List *)lfirst(lc);

    /* Do basic expression transformation (same as a ROW() expr) */
    sublist = transformExpressionList(pstate, sublist);

    /*
     * All the sublists must be the same length, *after* transformation
     * (which might expand '*' into multiple items).  The VALUES RTE can't
     * handle anything different.
     */
    if (sublist_length < 0) {
      /* Remember post-transformation length of first sublist */
      sublist_length = list_length(sublist);
      /* and allocate arrays for column-type info */
      coltype_lists = (List **)palloc0(sublist_length * sizeof(List *));
      coltypes = (Oid *)palloc0(sublist_length * sizeof(Oid));
    } else if (sublist_length != list_length(sublist)) {
      ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
                      errmsg("VALUES lists must all be the same length"),
                      errOmitLocation(true)));
    }

    exprsLists = lappend(exprsLists, sublist);

    i = 0;
    foreach (lc2, sublist) {
      Node *col = (Node *)lfirst(lc2);

      if (IsA(col, SetToDefault))
        ereport(
            ERROR,
            (errcode(ERRCODE_SYNTAX_ERROR),
             errmsg("DEFAULT can only appear in a VALUES list within INSERT"),
             errOmitLocation(true)));
      coltype_lists[i] = lappend_oid(coltype_lists[i], exprType(col));
      i++;
    }
  }

  /*
   * Now resolve the common types of the columns, and coerce everything to
   * those types.
   */
  for (i = 0; i < sublist_length; i++) {
    coltypes[i] = select_common_type(coltype_lists[i], "VALUES");
  }

  newExprsLists = NIL;
  foreach (lc, exprsLists) {
    List *sublist = (List *)lfirst(lc);
    List *newsublist = NIL;

    i = 0;
    foreach (lc2, sublist) {
      Node *col = (Node *)lfirst(lc2);

      col = coerce_to_common_type(pstate, col, coltypes[i], "VALUES");
      newsublist = lappend(newsublist, col);
      i++;
    }

    newExprsLists = lappend(newExprsLists, newsublist);
  }

  /*
   * Generate the VALUES RTE
   */
  rte = addRangeTableEntryForValues(pstate, newExprsLists, NULL, true);
  rtr = makeNode(RangeTblRef);
  /* assume new rte is at end */
  rtr->rtindex = list_length(pstate->p_rtable);
  Assert(rte == rt_fetch(rtr->rtindex, pstate->p_rtable));
  pstate->p_joinlist = lappend(pstate->p_joinlist, rtr);
  pstate->p_varnamespace = lappend(pstate->p_varnamespace, rte);

  /*
   * Generate a targetlist as though expanding "*"
   */
  Assert(pstate->p_next_resno == 1);
  qry->targetList = expandRelAttrs(pstate, rte, rtr->rtindex, 0, -1);

  /*
   * The grammar allows attaching ORDER BY, LIMIT, and FOR UPDATE to a
   * VALUES, so cope.
   */
  qry->sortClause = transformSortClause(
      pstate, stmt->sortClause, &qry->targetList, true, /* fix unknowns */
      false /* use SQL92 rules */);

  qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset, "OFFSET");
  qry->limitCount = transformLimitClause(pstate, stmt->limitCount, "LIMIT");

  /* CDB: Cursor position not available for errors below this point. */
  pstate->p_breadcrumb.node = NULL;

  if (stmt->lockingClause)
    ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
             errmsg("SELECT FOR UPDATE/SHARE cannot be applied to VALUES"),
             errOmitLocation(true)));

  if (Gp_role == GP_ROLE_DISPATCH) setQryDistributionPolicy(stmt, qry);

  /* handle any CREATE TABLE AS spec */
  qry->intoClause = NULL;
  if (stmt->intoClause) {
    qry->intoClause = stmt->intoClause;
    if (stmt->intoClause->colNames)
      applyColumnNames(qry->targetList, stmt->intoClause->colNames);
  }

  /*
   * There mustn't have been any table references in the expressions, else
   * strange things would happen, like Cartesian products of those tables
   * with the VALUES list.  We have to check this after parsing ORDER BY et
   * al since those could insert more junk.
   */
  if (list_length(pstate->p_joinlist) != 1)
    ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("VALUES must not contain table references"),
                    errOmitLocation(true)));

  /*
   * Another thing we can't currently support is NEW/OLD references in rules
   * --- seems we'd need something like SQL99's LATERAL construct to ensure
   * that the values would be available while evaluating the VALUES RTE.
   * This is a shame.  FIXME
   */
  if (list_length(pstate->p_rtable) != 1 &&
      contain_vars_of_level((Node *)newExprsLists, 0))
    ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("VALUES must not contain OLD or NEW references"),
                    errhint("Use SELECT ... UNION ALL ... instead."),
                    errOmitLocation(true)));

  qry->rtable = pstate->p_rtable;
  qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);

  qry->hasSubLinks = pstate->p_hasSubLinks;
  /* aggregates not allowed (but subselects are okay) */
  if (pstate->p_hasAggs)
    ereport(ERROR, (errcode(ERRCODE_GROUPING_ERROR),
                    errmsg("cannot use aggregate function in VALUES"),
                    errOmitLocation(true)));

  if (pstate->p_hasWindFuncs)
    ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
                    errmsg("cannot use window function in VALUES"),
                    errOmitLocation(true)));

  return qry;
}

/*
 * transformSetOperationStmt -
 *	  transforms a set-operations tree
 *
 * A set-operation tree is just a SELECT, but with UNION/INTERSECT/EXCEPT
 * structure to it.  We must transform each leaf SELECT and build up a top-
 * level Query that contains the leaf SELECTs as subqueries in its rangetable.
 * The tree of set operations is converted into the setOperations field of
 * the top-level Query.
 */
static Query *transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt) {
  Query *qry = makeNode(Query);
  SelectStmt *leftmostSelect;
  int leftmostRTI;
  Query *leftmostQuery;
  SetOperationStmt *sostmt;
  List *intoColNames = NIL;
  List *sortClause;
  Node *limitOffset;
  Node *limitCount;
  List *lockingClause;
  Node *node;
  ListCell *left_tlist, *lct, *lcm, *l;
  List *targetvars, *targetnames, *sv_relnamespace, *sv_varnamespace,
      *sv_rtable;
  RangeTblEntry *jrte;
  int tllen;
  List *colTypes, *colTypmods;

  qry->commandType = CMD_SELECT;

  /* process the WITH clause independently of all else */
  if (stmt->withClause != NULL) {
    qry->hasRecursive = stmt->withClause->recursive;
    qry->cteList = transformWithClause(pstate, stmt->withClause);
    qry->hasModifyingCTE = pstate->p_hasModifyingCTE;
  }

  /*
   * Find leftmost leaf SelectStmt; extract the one-time-only items from it
   * and from the top-level node.  (Most of the INTO options can be
   * transferred to the Query immediately, but intoColNames has to be saved
   * to apply below.)
   */
  leftmostSelect = stmt->larg;
  while (leftmostSelect && leftmostSelect->op != SETOP_NONE)
    leftmostSelect = leftmostSelect->larg;
  Assert(leftmostSelect && IsA(leftmostSelect, SelectStmt) &&
         leftmostSelect->larg == NULL);
  qry->intoClause = NULL;
  if (leftmostSelect->intoClause) {
    qry->intoClause = leftmostSelect->intoClause;
    intoColNames = leftmostSelect->intoClause->colNames;
  }

  /* clear this to prevent complaints in transformSetOperationTree() */
  leftmostSelect->intoClause = NULL;

  /*
   * These are not one-time, exactly, but we want to process them here and
   * not let transformSetOperationTree() see them --- else it'll just
   * recurse right back here!
   */
  sortClause = stmt->sortClause;
  limitOffset = stmt->limitOffset;
  limitCount = stmt->limitCount;
  lockingClause = stmt->lockingClause;

  stmt->sortClause = NIL;
  stmt->limitOffset = NULL;
  stmt->limitCount = NULL;
  stmt->lockingClause = NIL;

  /* We don't support FOR UPDATE/SHARE with set ops at the moment. */
  if (lockingClause)
    ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg(
                        "SELECT FOR UPDATE/SHARE is not allowed with "
                        "UNION/INTERSECT/EXCEPT"),
                    errOmitLocation(true)));

  /*
   * Before transforming the subtrees, we collect all the data types
   * and typmods by searching their targetList (ResTarget) or valuesClause.
   * This is necessary because choosing column types in leaf query
   * without knowing whole of tree may result in a wrong type. And this
   * situation goes an error that is against user's instinct. Instead,
   * we want to look at all the leavs at one time, and decide each column
   * types. The resjunk columns are not interesting, because type coercion
   * between queries is done only for each non-resjunk column in set operations.
   */
  colTypes = NIL;
  colTypmods = NIL;
  pstate->p_setopTypes = NIL;
  pstate->p_setopTypmods = NIL;
  collectSetopTypes(pstate, stmt, &colTypes, &colTypmods);
  Insist(list_length(colTypes) == list_length(colTypmods));
  forboth(lct, colTypes, lcm, colTypmods) {
    List *types = (List *)lfirst(lct);
    List *typmods = (List *)lfirst(lcm);
    ListCell *lct2, *lcm2;
    Oid ptype;
    int32 ptypmod;
    Oid restype;
    int32 restypmod;
    bool allsame, hasnontext;
    char *context;

    Insist(list_length(types) == list_length(typmods));

    context = (stmt->op == SETOP_UNION ? "UNION" : stmt->op == SETOP_INTERSECT
                                                       ? "INTERSECT"
                                                       : "EXCEPT");
    allsame = true;
    hasnontext = false;
    ptype = linitial_oid(types);
    ptypmod = linitial_int(typmods);
    forboth(lct2, types, lcm2, typmods) {
      Oid ntype = lfirst_oid(lct2);
      int32 ntypmod = lfirst_int(lcm2);

      /*
       * In the first iteration, ntype and ptype is the same element,
       * but we ignore it as it's not a big problem here.
       */
      if (!(ntype == ptype && ntypmod == ptypmod)) {
        /* if any is different, false */
        allsame = false;
      }
      /*
       * MPP-15619 - backwards compatibility with existing view definitions.
       *
       * Historically we would cast UNKNOWN to text for most union queries,
       * but there are many union cases where this historical behavior
       * resulted in unacceptable errors (MPP-11377).
       * To handle this we added additional code to resolve to a
       * consistent cast for unions, which is generally better and
       * handles more cases.  However, in order to deal with backwards
       * compatibility we have to deliberately hamstring this code and
       * cast UNKNOWN to text if the other colums are STRING_TYPE
       * even when some other datatype (such as name) might actually
       * be more natural.  This captures the set of views that
       * we previously supported prior to the fix for MPP-11377 and
       * thus is the set of views that we must not treat differently.
       * This might be removed when we are ready to change view definition.
       */
      if (ntype != UNKNOWNOID &&
          STRING_TYPE != TypeCategory(getBaseType(ntype)))
        hasnontext = true;
    }

    /*
     * Backward compatibility; Unfortunately, we cannot change
     * the old behavior of the part which was working without ERROR,
     * mostly for the view definition. See comments above for detail.
     * Setting InvalidOid for this column, the column type resolution
     * will be falling back to the old process.
     */
    if (!hasnontext) {
      restype = InvalidOid;
      restypmod = -1;
    } else {
      /*
       * Even if the types are all the same, we resolve the type
       * by select_common_type(), which casts domains to base types.
       * Ideally, the domain types should be preserved, but to keep
       * compatibility with older GPDB views, currently we don't change it.
       * This restriction will be solved once upgrade/view issues get clean.
       * See MPP-7509 for the issue.
       */
      restype = select_common_type(types, context);
      /*
       * If there's no common type, the last resort is TEXT.
       * See also select_common_type().
       */
      if (restype == UNKNOWNOID) {
        restype = TEXTOID;
        restypmod = -1;
      } else {
        /*
         * Essentially we preserve typmod only when all elements
         * are identical, otherwise default (-1).
         */
        if (allsame)
          restypmod = ptypmod;
        else
          restypmod = -1;
      }
    }

    pstate->p_setopTypes = lappend_oid(pstate->p_setopTypes, restype);
    pstate->p_setopTypmods = lappend_int(pstate->p_setopTypmods, restypmod);
  }

  /*
   * Recursively transform the components of the tree.
   */
  sostmt = (SetOperationStmt *)transformSetOperationTree(pstate, stmt);
  Assert(sostmt && IsA(sostmt, SetOperationStmt));
  qry->setOperations = (Node *)sostmt;

  /*
   * Re-find leftmost SELECT (now it's a sub-query in rangetable)
   */
  node = sostmt->larg;
  while (node && IsA(node, SetOperationStmt))
    node = ((SetOperationStmt *)node)->larg;
  Assert(node && IsA(node, RangeTblRef));
  leftmostRTI = ((RangeTblRef *)node)->rtindex;
  leftmostQuery = rt_fetch(leftmostRTI, pstate->p_rtable)->subquery;
  Assert(leftmostQuery != NULL);

  /* Copy transformed distribution policy to query */
  if (qry->intoClause) qry->intoPolicy = leftmostQuery->intoPolicy;

  /*
   * Generate dummy targetlist for outer query using column names of
   * leftmost select and common datatypes of topmost set operation. Also
   * make lists of the dummy vars and their names for use in parsing ORDER
   * BY.
   *
   * Note: we use leftmostRTI as the varno of the dummy variables. It
   * shouldn't matter too much which RT index they have, as long as they
   * have one that corresponds to a real RT entry; else funny things may
   * happen when the tree is mashed by rule rewriting.
   */
  qry->targetList = NIL;
  targetvars = NIL;
  targetnames = NIL;
  left_tlist = list_head(leftmostQuery->targetList);

  forboth(lct, sostmt->colTypes, lcm, sostmt->colTypmods) {
    Oid colType = lfirst_oid(lct);
    int32 colTypmod = lfirst_int(lcm);
    TargetEntry *lefttle = (TargetEntry *)lfirst(left_tlist);
    char *colName;
    TargetEntry *tle;
    Expr *expr;

    Assert(!lefttle->resjunk);
    colName = pstrdup(lefttle->resname);
    expr = (Expr *)makeVar(leftmostRTI, lefttle->resno, colType, colTypmod, 0);
    tle = makeTargetEntry(expr, (AttrNumber)pstate->p_next_resno++, colName,
                          false);
    qry->targetList = lappend(qry->targetList, tle);
    targetvars = lappend(targetvars, expr);
    targetnames = lappend(targetnames, makeString(colName));
    left_tlist = lnext(left_tlist);
  }

  /*
   * Coerce the UNKNOWN type for target entries to its right type here.
   */
  fixup_unknown_vars_in_setop(pstate, sostmt);

  /*
   * As a first step towards supporting sort clauses that are expressions
   * using the output columns, generate a varnamespace entry that makes the
   * output columns visible.	A Join RTE node is handy for this, since we
   * can easily control the Vars generated upon matches.
   *
   * Note: we don't yet do anything useful with such cases, but at least
   * "ORDER BY upper(foo)" will draw the right error message rather than
   * "foo not found".
   */
  jrte = addRangeTableEntryForJoin(NULL, targetnames, JOIN_INNER, targetvars,
                                   NULL, false);

  sv_rtable = pstate->p_rtable;
  pstate->p_rtable = list_make1(jrte);

  sv_relnamespace = pstate->p_relnamespace;
  pstate->p_relnamespace = NIL; /* no qualified names allowed */

  sv_varnamespace = pstate->p_varnamespace;
  pstate->p_varnamespace = list_make1(jrte);

  /*
   * For now, we don't support resjunk sort clauses on the output of a
   * setOperation tree --- you can only use the SQL92-spec options of
   * selecting an output column by name or number.  Enforce by checking that
   * transformSortClause doesn't add any items to tlist.
   */
  tllen = list_length(qry->targetList);

  qry->sortClause = transformSortClause(pstate, sortClause, &qry->targetList,
                                        false /* no unknowns expected */,
                                        false /* use SQL92 rules */);

  pstate->p_rtable = sv_rtable;
  pstate->p_relnamespace = sv_relnamespace;
  pstate->p_varnamespace = sv_varnamespace;

  if (tllen != list_length(qry->targetList))
    ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg(
                        "ORDER BY on a UNION/INTERSECT/EXCEPT result must be "
                        "on one of the result columns"),
                    errOmitLocation(true)));

  qry->limitOffset = transformLimitClause(pstate, limitOffset, "OFFSET");
  qry->limitCount = transformLimitClause(pstate, limitCount, "LIMIT");

  /* CDB: Cursor position not available for errors below this point. */
  pstate->p_breadcrumb.node = NULL;

  /*
   * Handle SELECT INTO/CREATE TABLE AS.
   *
   * Any column names from CREATE TABLE AS need to be attached to both the
   * top level and the leftmost subquery.  We do not do this earlier because
   * we do *not* want sortClause processing to be affected.
   */
  if (intoColNames) {
    applyColumnNames(qry->targetList, intoColNames);
    applyColumnNames(leftmostQuery->targetList, intoColNames);
  }

  qry->rtable = pstate->p_rtable;
  qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);

  qry->hasSubLinks = pstate->p_hasSubLinks;
  qry->hasAggs = pstate->p_hasAggs;
  if (pstate->p_hasAggs || qry->groupClause || qry->havingQual)
    parseCheckAggregates(pstate, qry);

  if (pstate->p_hasTblValueExpr) parseCheckTableFunctions(pstate, qry);

  foreach (l, lockingClause) {
    transformLockingClause(qry, (LockingClause *)lfirst(l));
  }

  return qry;
}

/*
 * transformSetOperationTree
 *		Recursively transform leaves and internal nodes of a set-op tree
 */
static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt) {
  Assert(stmt && IsA(stmt, SelectStmt));

  /*
   * Validity-check both leaf and internal SELECTs for disallowed ops.
   */
  if (stmt->intoClause)
    ereport(
        ERROR,
        (errcode(ERRCODE_SYNTAX_ERROR),
         errmsg(
             "INTO is only allowed on first SELECT of UNION/INTERSECT/EXCEPT"),
         errOmitLocation(true)));
  /* We don't support FOR UPDATE/SHARE with set ops at the moment. */
  if (stmt->lockingClause)
    ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg(
                        "SELECT FOR UPDATE/SHARE is not allowed with "
                        "UNION/INTERSECT/EXCEPT"),
                    errOmitLocation(true)));

  if (isSetopLeaf(stmt)) {
    /* Process leaf SELECT */
    List *selectList;
    Query *selectQuery;
    char selectName[32];
    RangeTblEntry *rte;
    RangeTblRef *rtr;

    /*
     * Transform SelectStmt into a Query.
     *
     * Note: previously transformed sub-queries don't affect the parsing
     * of this sub-query, because they are not in the toplevel pstate's
     * namespace list.
     */
    selectList = parse_sub_analyze((Node *)stmt, pstate);

    Assert(list_length(selectList) == 1);
    selectQuery = (Query *)linitial(selectList);
    Assert(IsA(selectQuery, Query));

    /*
     * Check for bogus references to Vars on the current query level (but
     * upper-level references are okay). Normally this can't happen
     * because the namespace will be empty, but it could happen if we are
     * inside a rule.
     */
    if (pstate->p_relnamespace || pstate->p_varnamespace) {
      if (contain_vars_of_level((Node *)selectQuery, 1))
        ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
                        errmsg(
                            "UNION/INTERSECT/EXCEPT member statement may not "
                            "refer to other relations of same query level"),
                        errOmitLocation(true)));
    }

    /*
     * Make the leaf query be a subquery in the top-level rangetable.
     */
    snprintf(selectName, sizeof(selectName), "*SELECT* %d",
             list_length(pstate->p_rtable) + 1);
    rte = addRangeTableEntryForSubquery(pstate, selectQuery,
                                        makeAlias(selectName, NIL), false);

    /*
     * Return a RangeTblRef to replace the SelectStmt in the set-op tree.
     */
    rtr = makeNode(RangeTblRef);
    /* assume new rte is at end */
    rtr->rtindex = list_length(pstate->p_rtable);
    Assert(rte == rt_fetch(rtr->rtindex, pstate->p_rtable));
    return (Node *)rtr;
  } else {
    /* Process an internal node (set operation node) */
    SetOperationStmt *op = makeNode(SetOperationStmt);
    List *lcoltypes;
    List *rcoltypes;
    List *lcoltypmods;
    List *rcoltypmods;
    ListCell *lct;
    ListCell *rct;
    ListCell *mct;
    ListCell *lcm;
    ListCell *rcm;
    ListCell *mcm;
    const char *context;

    context = (stmt->op == SETOP_UNION
                   ? "UNION"
                   : (stmt->op == SETOP_INTERSECT ? "INTERSECT" : "EXCEPT"));

    op->op = stmt->op;
    op->all = stmt->all;

    /*
     * Recursively transform the child nodes.
     */
    op->larg = transformSetOperationTree(pstate, stmt->larg);
    op->rarg = transformSetOperationTree(pstate, stmt->rarg);

    /*
     * Verify that the two children have the same number of non-junk
     * columns, and determine the types of the merged output columns.
     * At one time in past, this information was used to deduce
     * common data type, but now we don't; predict it beforehand,
     * since in some cases transformation of individual leaf query
     * hides what type the column should be from the whole of tree view.
     */
    getSetColTypes(pstate, op->larg, &lcoltypes, &lcoltypmods);
    getSetColTypes(pstate, op->rarg, &rcoltypes, &rcoltypmods);
    if (list_length(lcoltypes) != list_length(rcoltypes))
      ereport(ERROR,
              (errcode(ERRCODE_SYNTAX_ERROR),
               errmsg("each %s query must have the same number of columns",
                      context),
               errOmitLocation(true)));
    Assert(list_length(lcoltypes) == list_length(lcoltypmods));
    Assert(list_length(rcoltypes) == list_length(rcoltypmods));

    op->colTypes = NIL;
    op->colTypmods = NIL;
    /* We should have predicted types and typmods up to now */
    Assert(pstate->p_setopTypes && pstate->p_setopTypmods);
    Assert(list_length(pstate->p_setopTypes) ==
           list_length(pstate->p_setopTypmods));
    Assert(list_length(pstate->p_setopTypes) == list_length(lcoltypes));

    /* Iterate each column with tree candidates */
    lct = list_head(lcoltypes);
    rct = list_head(rcoltypes);
    lcm = list_head(lcoltypmods);
    rcm = list_head(rcoltypmods);
    forboth(mct, pstate->p_setopTypes, mcm, pstate->p_setopTypmods) {
      Oid lcoltype = lfirst_oid(lct);
      Oid rcoltype = lfirst_oid(rct);
      int32 lcoltypmod = lfirst_int(lcm);
      int32 rcoltypmod = lfirst_int(rcm);
      Oid rescoltype = lfirst_oid(mct);
      int32 rescoltypmod = lfirst_int(mcm);

      /*
       * If the preprocessed coltype is InvalidOid, we fall back
       * to the old style type resolution for backward
       * compatibility. See transformSetOperationStmt for the reason.
       */
      if (!OidIsValid(rescoltype)) {
        /* select common type, same as CASE et al */
        rescoltype =
            select_common_type(list_make2_oid(lcoltype, rcoltype), context);
        /* if same type and same typmod, use typmod; else default */
        if (lcoltype == rcoltype && lcoltypmod == rcoltypmod)
          rescoltypmod = lcoltypmod;
      }
      /* Set final decision */
      op->colTypes = lappend_oid(op->colTypes, rescoltype);
      op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
      lct = lnext(lct);
      lcm = lnext(lcm);
      rct = lnext(rct);
      rcm = lnext(rcm);
    }

    return (Node *)op;
  }
}

/*
 * isSetopLeaf
 *  returns true if the statement is set operation tree leaf.
 */
static bool isSetopLeaf(SelectStmt *stmt) {
  Assert(stmt && IsA(stmt, SelectStmt));
  /*
   * If an internal node of a set-op tree has ORDER BY, UPDATE, or LIMIT
   * clauses attached, we need to treat it like a leaf node to generate an
   * independent sub-Query tree.	Otherwise, it can be represented by a
   * SetOperationStmt node underneath the parent Query.
   */
  if (stmt->op == SETOP_NONE) {
    Assert(stmt->larg == NULL && stmt->rarg == NULL);
    return true;
  } else {
    Assert(stmt->larg != NULL && stmt->rarg != NULL);
    if (stmt->sortClause || stmt->limitOffset || stmt->limitCount ||
        stmt->lockingClause)
      return true;
    else
      return false;
  }
}

/*
 * collectSetopTypes
 *  transforms the statement partially and collect data type oid
 * and typmod from targetlist recursively. In set operations, the
 * final data type should be determined from the total tree view,
 * so we traverse the tree and collect types naively (without coercing)
 * and use the information for later column type decision.
 * types and typmods are output parameter, and the returned values
 * are List of List which contain column number of elements as the
 * first dimension, and leaf number of elements as the second dimension.
 */
static void collectSetopTypes(ParseState *pstate, SelectStmt *stmt,
                              List **types, List **typmods) {
  if (isSetopLeaf(stmt)) {
    ParseState *parentstate = pstate;
    SelectStmt *select_stmt = stmt;
    List *tlist;
    ListCell *lc, *lct, *lcm;

    /* Copy them just in case */
    pstate = make_parsestate(parentstate);
    stmt = copyObject(select_stmt);

    if (stmt->valuesLists) {
      /* in VALUES query, we can transform all */
      tlist = transformValuesClause(pstate, stmt)->targetList;
    } else {
      /* transform only tragetList */
      transformFromClause(pstate, stmt->fromClause);
      tlist = transformTargetList(pstate, stmt->targetList);
    }

    if (*types == NIL) {
      Assert(*typmods == NIL);
      /* Construct List of List for numbers of tlist */
      foreach (lc, tlist) {
        *types = lappend(*types, NIL);
        *typmods = lappend(*typmods, NIL);
      }
    } else if (list_length(*types) != list_length(tlist)) {
      /*
       * Must be an error in later process.
       * Nothing to do in this preprocess (not an assert.)
       */
      free_parsestate(&pstate);
      pfree(stmt);
      return;
    }
    lct = list_head(*types);
    lcm = list_head(*typmods);
    foreach (lc, tlist) {
      TargetEntry *tle = (TargetEntry *)lfirst(lc);
      List *typelist = (List *)lfirst(lct);
      List *typmodlist = (List *)lfirst(lcm);

      /* Keep back to the original List */
      lfirst(lct) = lappend_oid(typelist, exprType((Node *)tle->expr));
      lfirst(lcm) = lappend_int(typmodlist, exprTypmod((Node *)tle->expr));

      lct = lnext(lct);
      lcm = lnext(lcm);
    }
    /* They're not needed anymore */
    free_parsestate(&pstate);
    pfree(stmt);
  } else {
    /* just recurse to the leaf */
    collectSetopTypes(pstate, stmt->larg, types, typmods);
    collectSetopTypes(pstate, stmt->rarg, types, typmods);
  }
}

/*
 * getSetColTypes
 *	  Get output column types/typmods of an (already transformed) set-op
 *node
 */
static void getSetColTypes(ParseState *pstate, Node *node, List **colTypes,
                           List **colTypmods) {
  *colTypes = NIL;
  *colTypmods = NIL;
  if (IsA(node, RangeTblRef)) {
    RangeTblRef *rtr = (RangeTblRef *)node;
    RangeTblEntry *rte = rt_fetch(rtr->rtindex, pstate->p_rtable);
    Query *selectQuery = rte->subquery;
    ListCell *tl;

    Assert(selectQuery != NULL);
    /* Get types of non-junk columns */
    foreach (tl, selectQuery->targetList) {
      TargetEntry *tle = (TargetEntry *)lfirst(tl);

      if (tle->resjunk) continue;
      *colTypes = lappend_oid(*colTypes, exprType((Node *)tle->expr));
      *colTypmods = lappend_int(*colTypmods, exprTypmod((Node *)tle->expr));
    }
  } else if (IsA(node, SetOperationStmt)) {
    SetOperationStmt *op = (SetOperationStmt *)node;

    /* Result already computed during transformation of node */
    Assert(op->colTypes != NIL);
    *colTypes = op->colTypes;
    *colTypmods = op->colTypmods;
  } else
    elog(ERROR, "unrecognized node type: %d", (int)nodeTag(node));
}

/* Attach column names from a ColumnDef list to a TargetEntry list */
static void applyColumnNames(List *dst, List *src) {
  ListCell *dst_item;
  ListCell *src_item;

  src_item = list_head(src);

  foreach (dst_item, dst) {
    TargetEntry *d = (TargetEntry *)lfirst(dst_item);
    ColumnDef *s;

    /* junk targets don't count */
    if (d->resjunk) continue;

    /* fewer ColumnDefs than target entries is OK */
    if (src_item == NULL) break;

    s = (ColumnDef *)lfirst(src_item);
    src_item = lnext(src_item);

    d->resname = pstrdup(s->colname);
  }

  /* more ColumnDefs than target entries is not OK */
  if (src_item != NULL)
    ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
                    errmsg("CREATE TABLE AS specifies too many column names"),
                    errOmitLocation(true)));
}

/*
 * transformUpdateStmt -
 *	  transforms an update statement
 */
static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt) {
  Query *qry = makeNode(Query);
  Node *qual;
  ListCell *origTargetList;
  ListCell *tl;

  qry->commandType = CMD_UPDATE;
  pstate->p_is_update = true;

  /* setup database name for use of magma operations */
  MemoryContext oldContext = MemoryContextSwitchTo(MessageContext);

  int isGraph = parseAndTransformAsGraph(pstate, stmt->relation);

  char *dbname = stmt->relation->catalogname;
  database =
      (dbname != NULL) ? pstrdup(dbname) : get_database_name(MyDatabaseId);
  MemoryContextSwitchTo(oldContext);

  qry->resultRelation = setTargetTable(
      pstate, stmt->relation, interpretInhOption(stmt->relation->inhOpt), true,
      ACL_UPDATE);
  if(isGraph)
    pstate->p_target_rangetblentry->graphName = stmt->relation->schemaname ?
        pstrdup(stmt->relation->schemaname) : NULL;
  /*
   * the FROM clause is non-standard SQL syntax. We used to be able to do
   * this with REPLACE in POSTQUEL so we keep the feature.
   */
  transformFromClause(pstate, stmt->fromClause);

  qry->targetList = transformTargetList(pstate, stmt->targetList);

  qual = transformWhereClause(pstate, stmt->whereClause, "WHERE");

/*
 * MPP-2506 [insert/update/delete] RETURNING clause not supported:
 *   We have problems processing the returning clause, so for now we have
 *   simply removed it and replaced it with an error message.
 */
#ifdef MPP_RETURNING_NOT_SUPPORTED
  if (stmt->returningList) {
    ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg(
                        "The RETURNING clause of the UPDATE statement is not "
                        "supported in this version of Greenplum Database."),
                    errOmitLocation(true)));
  }
#else
  qry->returningList = transformReturningList(pstate, stmt->returningList);
#endif

  /*
   * CDB: Untyped Const or Param nodes in a subquery in the FROM clause
   * could have been assigned proper types when we transformed the WHERE
   * clause or targetlist above.  Bring targetlist Var types up to date.
   */
  if (stmt->fromClause) {
    fixup_unknown_vars_in_targetlist(pstate, qry->targetList);
    fixup_unknown_vars_in_targetlist(pstate, qry->returningList);
  }

  /* CDB: Cursor position not available for errors below this point. */
  pstate->p_breadcrumb.node = NULL;

  qry->rtable = pstate->p_rtable;
  qry->jointree = makeFromExpr(pstate->p_joinlist, qual);

  qry->hasSubLinks = pstate->p_hasSubLinks;

  /*
   * Top-level aggregates are simply disallowed in UPDATE, per spec. (From
   * an implementation point of view, this is forced because the implicit
   * ctid reference would otherwise be an ungrouped variable.)
   */
  if (pstate->p_hasAggs)
    ereport(ERROR, (errcode(ERRCODE_GROUPING_ERROR),
                    errmsg("cannot use aggregate function in UPDATE"),
                    errOmitLocation(true)));

  if (pstate->p_hasWindFuncs)
    ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
                    errmsg("cannot use window function in UPDATE"),
                    errOmitLocation(true)));

  /*
   * Now we are done with SELECT-like processing, and can get on with
   * transforming the target list to match the UPDATE target columns.
   */

  /* Prepare to assign non-conflicting resnos to resjunk attributes */
  if (pstate->p_next_resno <= pstate->p_target_relation->rd_rel->relnatts)
    pstate->p_next_resno = pstate->p_target_relation->rd_rel->relnatts + 1;

  /* Prepare non-junk columns for assignment to target table */
  origTargetList = list_head(stmt->targetList);

  int pknatts = 0;
  AttrNumber *pkattno = NULL;
  /* get primary keys for check not supported update pkey column */
  if (RelationIsExternal(pstate->p_target_relation)) {
    Oid reloid = RelationGetRelid(pstate->p_target_relation);
    ExtTableEntry *extentry = GetExtTableEntry(reloid);
    char *formatter = getExtTblFormatterTypeInFmtOptsStr(extentry->fmtopts);
    if (formatter && pg_strncasecmp(formatter, "magma", strlen("magma")) == 0) {
      ConstraintGetPrimaryKeys(reloid, &pknatts, &pkattno);
    }
  }

  foreach (tl, qry->targetList) {
    TargetEntry *tle = (TargetEntry *)lfirst(tl);
    ResTarget *origTarget;
    int attrno;

    if (tle->resjunk) {
      /*
       * Resjunk nodes need no additional processing, but be sure they
       * have resnos that do not match any target columns; else rewriter
       * or planner might get confused.  They don't need a resname
       * either.
       */
      tle->resno = (AttrNumber)pstate->p_next_resno++;
      tle->resname = NULL;
      continue;
    }
    if (origTargetList == NULL)
      elog(ERROR, "UPDATE target count mismatch --- internal error");
    origTarget = (ResTarget *)lfirst(origTargetList);
    Assert(IsA(origTarget, ResTarget));

    /* CDB: Drop a breadcrumb in case of error. */
    pstate->p_breadcrumb.node = (Node *)origTarget;

    attrno = attnameAttNum(pstate->p_target_relation, origTarget->name, true);
    if (attrno == InvalidAttrNumber)
      ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN),
                      errmsg("column \"%s\" of relation \"%s\" does not exist",
                             origTarget->name, RelationGetRelationName(
                                                   pstate->p_target_relation)),
                      errOmitLocation(true),
                      parser_errposition(pstate, origTarget->location)));
    /*
     * if there are primary keys, then check whether the targetlist contains
     * primary keys, cannot update primary key columns.
     */
    if (pknatts >= 1) {
      for (int j = 0; j < pknatts; ++j) {
        if (pkattno[j] == attrno) {
          ereport(ERROR, (errcode(ERRCODE_UNIQUE_VIOLATION),
                          errmsg(
                              "cannot update primary key column \"%s\""
                              " of relation \"%s\" in \"magmatp or magmaap\" format",
                              origTarget->name, RelationGetRelationName(
                                                    pstate->p_target_relation)),
                          errOmitLocation(true),
                          parser_errposition(pstate, origTarget->location)));
        } else {
          continue;
        }
      }
    }
    updateTargetListEntry(pstate, tle, origTarget->name, attrno,
                          origTarget->indirection, origTarget->location);

    origTargetList = lnext(origTargetList);
  }
  if (origTargetList != NULL)
    elog(ERROR, "UPDATE target count mismatch --- internal error");

  return qry;
}

/*
 * MPP-2506 [insert/update/delete] RETURNING clause not supported:
 *   We have problems processing the returning clause, so for now we have
 *   simply removed it and replaced it with an error message.
 */
#ifndef MPP_RETURNING_NOT_SUPPORTED
/*
 * transformReturningList -
 *	handle a RETURNING clause in INSERT/UPDATE/DELETE
 */
static List *transformReturningList(ParseState *pstate, List *returningList) {
  List *rlist;
  int save_next_resno;
  bool save_hasAggs;
  int length_rtable;

  if (returningList == NIL) return NIL; /* nothing to do */

  /*
   * We need to assign resnos starting at one in the RETURNING list. Save
   * and restore the main tlist's value of p_next_resno, just in case
   * someone looks at it later (probably won't happen).
   */
  save_next_resno = pstate->p_next_resno;
  pstate->p_next_resno = 1;

  /* save other state so that we can detect disallowed stuff */
  save_hasAggs = pstate->p_hasAggs;
  pstate->p_hasAggs = false;
  length_rtable = list_length(pstate->p_rtable);

  /* transform RETURNING identically to a SELECT targetlist */
  rlist = transformTargetList(pstate, returningList);

  /* CDB: Cursor position not available for errors below this point. */
  pstate->p_breadcrumb.node = NULL;

  /* check for disallowed stuff */

  /* aggregates not allowed (but subselects are okay) */
  if (pstate->p_hasAggs)
    ereport(ERROR, (errcode(ERRCODE_GROUPING_ERROR),
                    errmsg("cannot use aggregate function in RETURNING")));

  if (pstate->p_hasWindFuncs)
    ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
                    errmsg("cannot use window function in RETURNING")));

  /* no new relation references please */
  if (list_length(pstate->p_rtable) != length_rtable)
    ereport(
        ERROR,
        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
         errmsg("RETURNING may not contain references to other relations")));

  /* mark column origins */
  markTargetListOrigins(pstate, rlist);

  /* restore state */
  pstate->p_next_resno = save_next_resno;
  pstate->p_hasAggs = save_hasAggs;

  return rlist;
}
#endif

/*
 * transformAlterTable_all_PartitionStmt -
 *	transform an Alter Table Statement for some Partition operation
 */
static AlterTableCmd *transformAlterTable_all_PartitionStmt(
    ParseState *pstate, AlterTableStmt *stmt, CreateStmtContext *pCxt,
    AlterTableCmd *cmd, List **extras_before, List **extras_after) {
  AlterPartitionCmd *pc = (AlterPartitionCmd *)cmd->def;
  AlterPartitionCmd *pci = pc;
  AlterPartitionId *pid = (AlterPartitionId *)pci->partid;
  AlterTableCmd *atc1 = cmd;
  RangeVar *rv = stmt->relation;
  PartitionNode *pNode = NULL;
  PartitionNode *prevNode = NULL;
  int partDepth = 0;
  Oid par_oid = InvalidOid;
  StringInfoData sid1, sid2;

  stmt->greenWay = true;

  if (atc1->subtype == AT_PartAlter) {
    PgPartRule *prule = NULL;
    char *lrelname;
    Relation rel = heap_openrv(rv, AccessShareLock);

    initStringInfo(&sid1);
    initStringInfo(&sid2);

    appendStringInfo(&sid1, "relation \"%s\"", RelationGetRelationName(rel));

    lrelname = sid1.data;

    pNode = RelationBuildPartitionDesc(rel, false);

    if (!pNode)
      ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT),
                      errmsg("%s is not partitioned", lrelname)));

    /* Processes nested ALTER (if it exists) */
    while (1) {
      AlterPartitionId *pid2 = NULL;

      if (atc1->subtype != AT_PartAlter) {
        rv = makeRangeVar(NULL /*catalogname*/,
                          get_namespace_name(RelationGetNamespace(rel)),
                          pstrdup(RelationGetRelationName(rel)), -1);
        heap_close(rel, AccessShareLock);
        rel = NULL;
        break;
      }

      pid2 = (AlterPartitionId *)pci->partid;

      if (pid2 && (pid2->idtype == AT_AP_IDValue)) {
        List *vallist = (List *)pid2->partiddef;
        pid2->partiddef = (Node *)transformExpressionList(pstate, vallist);
      }

      partDepth++;

      if (!pNode)
        prule = NULL;
      else
        prule = get_part_rule1(rel, pid2, false, true, CurrentMemoryContext,
                               NULL, pNode, sid1.data, NULL);

      if (prule && prule->topRule && prule->topRule->children) {
        prevNode = pNode;
        pNode = prule->topRule->children;
        par_oid = RelationGetRelid(rel);

        /*
         * Don't hold a long lock -- lock on the master is
         * sufficient
         */
        heap_close(rel, AccessShareLock);
        rel = heap_open(prule->topRule->parchildrelid, AccessShareLock);

        appendStringInfo(&sid2, "partition%s of %s", prule->partIdStr,
                         sid1.data);
        truncateStringInfo(&sid1, 0);
        appendStringInfo(&sid1, "%s", sid2.data);
        truncateStringInfo(&sid2, 0);
      } else {
        prevNode = pNode;
        pNode = NULL;
      }

      atc1 = (AlterTableCmd *)pci->arg1;
      pci = (AlterPartitionCmd *)atc1->def;
    }        /* end while */
    if (rel) /* No need to hold onto the lock -- see above */
      heap_close(rel, AccessShareLock);
  } /* end if alter */

  switch (atc1->subtype) {
    case AT_PartAdd:         /* Add */
    case AT_PartSetTemplate: /* Set Subpartn Template */

      if (pci->arg2) /* could be null for settemplate... */
      {
        AlterPartitionCmd *pc2 = (AlterPartitionCmd *)pci->arg2;
        InhRelation *inh = makeNode(InhRelation);
        List *cl = NIL;

        Assert(IsA(pc2->arg2, List));

        inh->relation = copyObject(rv);
        inh->options = list_make3_int(CREATE_TABLE_LIKE_INCLUDING_DEFAULTS,
                                      CREATE_TABLE_LIKE_INCLUDING_CONSTRAINTS,
                                      CREATE_TABLE_LIKE_INCLUDING_INDEXES);
        /*
         * fill in remaining fields from parse time (gram.y):
         * the new partition is LIKE the parent and it
         * inherits from it
         */
        if (IsA(linitial((List *)pc2->arg2), CreateExternalStmt)) {
          CreateExternalStmt *ct;
          ct = (CreateExternalStmt *)linitial((List *)pc2->arg2);
          ct->base.tableElts = lappend(ct->base.tableElts, inh);
          cl = list_make1(ct);
        } else {
          CreateStmt *ct;
          ct = (CreateStmt *)linitial((List *)pc2->arg2);
          ct->base.tableElts = lappend(ct->base.tableElts, inh);
          cl = list_make1(ct);
        }

        pc2->arg2 = (Node *)cl;
      }
    case AT_PartCoalesce: /* Coalesce */
    case AT_PartDrop:     /* Drop */
    case AT_PartExchange: /* Exchange */
    case AT_PartMerge:    /* Merge */
    case AT_PartModify:   /* Modify */
    case AT_PartRename:   /* Rename */
    case AT_PartTruncate: /* Truncate */
    case AT_PartSplit:    /* Split */
      /* MPP-4011: get right pid for FOR(value) */
      pid = (AlterPartitionId *)pci->partid;
      if (pid && (pid->idtype == AT_AP_IDValue)) {
        List *vallist = (List *)pid->partiddef;
        pid->partiddef = (Node *)transformExpressionList(pstate, vallist);
      }
      break;
    default:
      break;
  }
  /* transform boundary specifications at execute time */
  return cmd;
} /* end transformAlterTable_all_PartitionStmt */

/*
 * transformAlterTableStmt -
 *	transform an Alter Table Statement
 */
static Query *transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt,
                                      List **extras_before,
                                      List **extras_after) {
  CreateStmtContext cxt;
  Query *qry;
  ListCell *lcmd, *l;
  List *newcmds = NIL;
  bool skipValidation = true;
  AlterTableCmd *newcmd;

  cxt.stmtType = "ALTER TABLE";
  cxt.isExternalTable = false;
  cxt.relation = stmt->relation;
  cxt.inhRelations = NIL;
  cxt.isalter = true;
  cxt.hasoids = false; /* need not be right */
  cxt.columns = NIL;
  cxt.ckconstraints = NIL;
  cxt.fkconstraints = NIL;
  cxt.ixconstraints = NIL;
  cxt.inh_indexes = NIL;
  cxt.blist = NIL;
  cxt.alist = NIL;
  cxt.dlist = NIL; /* used by transformCreateStmt, not here */
  cxt.pkey = NULL;

  /*
   * The only subtypes that currently require parse transformation handling
   * are ADD COLUMN and ADD CONSTRAINT.  These largely re-use code from
   * CREATE TABLE.
   * And ALTER TABLE ... <operator> PARTITION ...
   */
  foreach (lcmd, stmt->cmds) {
    AlterTableCmd *cmd = (AlterTableCmd *)lfirst(lcmd);

    switch (cmd->subtype) {
      case AT_AddColumn: {
        ColumnDef *def = (ColumnDef *)cmd->def;

        Assert(IsA(cmd->def, ColumnDef));

        /*
         * Disallow adding a column with primary key constraint
         */
        if (Gp_role == GP_ROLE_DISPATCH) {
          ListCell *c;
          foreach (c, def->constraints) {
            Constraint *cons = (Constraint *)lfirst(c);
            if (cons->contype == CONSTR_PRIMARY)
              elog(ERROR,
                   "Cannot add column with primary "
                   "key constraint");
            if (cons->contype == CONSTR_UNIQUE)
              elog(ERROR,
                   "Cannot add column with unique "
                   "constraint");
          }
        }

        transformColumnDefinition(pstate, &cxt, (ColumnDef *)cmd->def);

        /*
         * If the column has a non-null default, we can't skip
         * validation of foreign keys.
         */
        if (((ColumnDef *)cmd->def)->raw_default != NULL)
          skipValidation = false;

        newcmds = lappend(newcmds, cmd);

        /*
         * Convert an ADD COLUMN ... NOT NULL constraint to a
         * separate command
         */
        if (def->is_not_null) {
          /* Remove NOT NULL from AddColumn */
          def->is_not_null = false;

          /* Add as a separate AlterTableCmd */
          newcmd = makeNode(AlterTableCmd);
          newcmd->subtype = AT_SetNotNull;
          newcmd->name = pstrdup(def->colname);
          newcmds = lappend(newcmds, newcmd);
        }

        /*
         * All constraints are processed in other ways. Remove the
         * original list
         */
        def->constraints = NIL;

        break;
      }
      case AT_AddConstraint:

        /*
         * The original AddConstraint cmd node doesn't go to newcmds
         */

        if (IsA(cmd->def, Constraint))
          transformTableConstraint(pstate, &cxt, (Constraint *)cmd->def);
        else if (IsA(cmd->def, FkConstraint)) {
          cxt.fkconstraints = lappend(cxt.fkconstraints, cmd->def);

          /* GPDB: always skip validation of foreign keys */
          skipValidation = true;
        } else
          elog(ERROR, "unrecognized node type: %d", (int)nodeTag(cmd->def));
        break;

      case AT_ProcessedConstraint:

        /*
         * Already-transformed ADD CONSTRAINT, so just make it look
         * like the standard case.
         */
        cmd->subtype = AT_AddConstraint;
        newcmds = lappend(newcmds, cmd);
        break;

      /* CDB: Partitioned Tables */
      case AT_PartAlter:       /* Alter */
      case AT_PartAdd:         /* Add */
      case AT_PartCoalesce:    /* Coalesce */
      case AT_PartDrop:        /* Drop */
      case AT_PartExchange:    /* Exchange */
      case AT_PartMerge:       /* Merge */
      case AT_PartModify:      /* Modify */
      case AT_PartRename:      /* Rename */
      case AT_PartSetTemplate: /* Set Subpartition Template */
      case AT_PartSplit:       /* Split */
      case AT_PartTruncate:    /* Truncate */
      {
        cmd = transformAlterTable_all_PartitionStmt(
            pstate, stmt, &cxt, cmd, extras_before, extras_after);

        newcmds = lappend(newcmds, cmd);
        break;
      }
      default:
        newcmds = lappend(newcmds, cmd);
        break;
    }
  }

  /*
   * transformIndexConstraints wants cxt.alist to contain only index
   * statements, so transfer anything we already have into extras_after
   * immediately.
   */
  *extras_after = list_concat(cxt.alist, *extras_after);
  cxt.alist = NIL;

  /* Postprocess index and FK constraints */
  transformIndexConstraints(pstate, &cxt, false);

  transformFKConstraints(pstate, &cxt, skipValidation, true);

  /*
   * Push any index-creation commands into the ALTER, so that they can be
   * scheduled nicely by tablecmds.c.
   */
  foreach (l, cxt.alist) {
    Node *idxstmt = (Node *)lfirst(l);

    Assert(IsA(idxstmt, IndexStmt));
    newcmd = makeNode(AlterTableCmd);
    newcmd->subtype = AT_AddIndex;
    newcmd->def = idxstmt;
    newcmds = lappend(newcmds, newcmd);
  }
  cxt.alist = NIL;

  /* Append any CHECK or FK constraints to the commands list */
  foreach (l, cxt.ckconstraints) {
    newcmd = makeNode(AlterTableCmd);
    newcmd->subtype = AT_AddConstraint;
    newcmd->def = (Node *)lfirst(l);
    newcmds = lappend(newcmds, newcmd);
  }
  foreach (l, cxt.fkconstraints) {
    newcmd = makeNode(AlterTableCmd);
    newcmd->subtype = AT_AddConstraint;
    newcmd->def = (Node *)lfirst(l);
    newcmds = lappend(newcmds, newcmd);
  }

  /* Update statement's commands list */
  stmt->cmds = newcmds;

  qry = makeNode(Query);
  qry->commandType = CMD_UTILITY;
  qry->utilityStmt = (Node *)stmt;

  *extras_before = list_concat(*extras_before, cxt.blist);
  *extras_after = list_concat(cxt.alist, *extras_after);

  return qry;
}

/*
 * transformDeclareCursorStmt -
 *	transform a DECLARE CURSOR Statement
 *
 * DECLARE CURSOR is a hybrid case: it's an optimizable statement (in fact not
 * significantly different from a SELECT) as far as parsing/rewriting/planning
 * are concerned, but it's not passed to the executor and so in that sense is
 * a utility statement.  We transform it into a Query exactly as if it were
 * a SELECT, then stick the original DeclareCursorStmt into the utilityStmt
 * field to carry the cursor name and options.
 */
static Query *transformDeclareCursorStmt(ParseState *pstate,
                                         DeclareCursorStmt *stmt) {
  Query *result = makeNode(Query);
  List *extras_before = NIL, *extras_after = NIL;

  result->commandType = CMD_UTILITY;
  result->utilityStmt = (Node *)stmt;

  /*
   * Don't allow both SCROLL and NO SCROLL to be specified
   */
  if ((stmt->options & CURSOR_OPT_SCROLL) &&
      (stmt->options & CURSOR_OPT_NO_SCROLL))
    ereport(ERROR, (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
                    errmsg("cannot specify both SCROLL and NO SCROLL")));

  result = transformStmt(pstate, stmt->query, &extras_before, &extras_after);

  /* Shouldn't get any extras, since grammar only allows SelectStmt */
  if (extras_before || extras_after)
    elog(ERROR, "unexpected extra stuff in cursor statement");

  /* Grammar should not have allowed anything but SELECT */
  if (!IsA(result, Query) || result->commandType != CMD_SELECT ||
      result->utilityStmt != NULL)
    elog(ERROR, "unexpected non-SELECT command in DECLARE CURSOR");

  /* But we must explicitly disallow DECLARE CURSOR ... SELECT INTO */
  if (result->intoClause)
    ereport(ERROR, (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
                    errmsg("DECLARE CURSOR cannot specify INTO")));

  /* We won't need the raw querytree any more */
  stmt->query = NULL;

  stmt->is_simply_updatable = isSimplyUpdatableQuery(result);

  result->utilityStmt = (Node *)stmt;

  return result;
}

/*
 * isSimplyUpdatableQuery -
 *  determine whether a query is a simply updatable scan of a relation
 *
 * A query is simply updatable if, and only if, it...
 * - has no window clauses
 * - has no sort clauses
 * - has no grouping, having, distinct clauses, or simple aggregates
 * - has no subqueries
 * - has no LIMIT/OFFSET
 * - references only one range table (i.e. no joins, self-joins)
 *   - this range table must itself be updatable
 *
 */
static bool isSimplyUpdatableQuery(Query *query) {
  Assert(query->commandType == CMD_SELECT);
  if (query->windowClause == NIL && query->sortClause == NIL &&
      query->groupClause == NIL && query->havingQual == NULL &&
      query->distinctClause == NIL && !query->hasAggs && !query->hasSubLinks &&
      query->limitCount == NULL && query->limitOffset == NULL &&
      list_length(query->rtable) == 1) {
    RangeTblEntry *rte = (RangeTblEntry *)linitial(query->rtable);
    if (isSimplyUpdatableRelation(rte->relid)) return true;
  }
  return false;
}

static Query *transformPrepareStmt(ParseState *pstate, PrepareStmt *stmt) {
  Query *result = makeNode(Query);
  List *argtype_oids;   /* argtype OIDs in a list */
  Oid *argtoids = NULL; /* and as an array */
  int nargs;
  List *queries;
  int i;

  result->commandType = CMD_UTILITY;
  result->utilityStmt = (Node *)stmt;

  /* Transform list of TypeNames to list (and array) of type OIDs */
  nargs = list_length(stmt->argtypes);

  if (nargs) {
    ListCell *l;

    argtoids = (Oid *)palloc(nargs * sizeof(Oid));
    i = 0;

    foreach (l, stmt->argtypes) {
      TypeName *tn = lfirst(l);
      Oid toid = typenameTypeId(pstate, tn);

      /* Pseudotypes are not valid parameters to PREPARE */
      if (get_typtype(toid) == TYPTYPE_PSEUDO) {
        ereport(ERROR,
                (errcode(ERRCODE_INDETERMINATE_DATATYPE),
                 errmsg("type \"%s\" is not a valid parameter for PREPARE",
                        TypeNameToString(tn))));
      }

      argtoids[i++] = toid;
    }
  }

  /*
   * Analyze the statement using these parameter types (any parameters
   * passed in from above us will not be visible to it), allowing
   * information about unknown parameters to be deduced from context.
   */
  queries = parse_analyze_varparams((Node *)stmt->query, pstate->p_sourcetext,
                                    &argtoids, &nargs);

  /*
   * Shouldn't get any extra statements, since grammar only allows
   * OptimizableStmt
   */
  if (list_length(queries) != 1)
    elog(ERROR, "unexpected extra stuff in prepared statement");

  /*
   * Check that all parameter types were determined, and convert the array
   * of OIDs into a list for storage.
   */
  argtype_oids = NIL;
  for (i = 0; i < nargs; i++) {
    Oid argtype = argtoids[i];

    if (argtype == InvalidOid || argtype == UNKNOWNOID)
      ereport(ERROR, (errcode(ERRCODE_INDETERMINATE_DATATYPE),
                      errmsg("could not determine data type of parameter $%d",
                             i + 1)));

    argtype_oids = lappend_oid(argtype_oids, argtype);
  }

  stmt->argtype_oids = argtype_oids;
  stmt->query = linitial(queries);
  return result;
}

static Query *transformExecuteStmt(ParseState *pstate, ExecuteStmt *stmt) {
  Query *result = makeNode(Query);
  List *paramtypes;

  result->commandType = CMD_UTILITY;
  result->utilityStmt = (Node *)stmt;

  paramtypes = FetchPreparedStatementParams(stmt->name);

  if (stmt->params || paramtypes) {
    int nparams = list_length(stmt->params);
    int nexpected = list_length(paramtypes);
    ListCell *l, *l2;
    int i = 1;

    if (nparams != nexpected)
      ereport(
          ERROR,
          (errcode(ERRCODE_SYNTAX_ERROR),
           errmsg("wrong number of parameters for prepared statement \"%s\"",
                  stmt->name),
           errdetail("Expected %d parameters but got %d.", nexpected, nparams),
           errOmitLocation(true)));

    forboth(l, stmt->params, l2, paramtypes) {
      Node *expr = lfirst(l);
      Oid expected_type_id = lfirst_oid(l2);
      Oid given_type_id;

      expr = transformExpr(pstate, expr);

      /* Cannot contain subselects or aggregates */
      if (pstate->p_hasSubLinks)
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        errmsg("cannot use subquery in EXECUTE parameter")));
      if (pstate->p_hasAggs)
        ereport(ERROR,
                (errcode(ERRCODE_GROUPING_ERROR),
                 errmsg("cannot use aggregate function in EXECUTE parameter")));
      if (pstate->p_hasWindFuncs)
        ereport(ERROR,
                (errcode(ERRCODE_SYNTAX_ERROR),
                 errmsg("cannot use window function in EXECUTE parameter")));

      given_type_id = exprType(expr);

      expr = coerce_to_target_type(pstate, expr, given_type_id,
                                   expected_type_id, -1, COERCION_ASSIGNMENT,
                                   COERCE_IMPLICIT_CAST, -1);

      if (expr == NULL)
        ereport(ERROR,
                (errcode(ERRCODE_DATATYPE_MISMATCH),
                 errmsg(
                     "parameter $%d of type %s cannot be coerced to the "
                     "expected type %s",
                     i, format_type_be(given_type_id),
                     format_type_be(expected_type_id)),
                 errhint("You will need to rewrite or cast the expression."),
                 errOmitLocation(true)));

      lfirst(l) = expr;
      i++;
    }
  }

  return result;
}

/* exported so planner can check again after rewriting, query pullup, etc */
void CheckSelectLocking(Query *qry) {
  if (qry->setOperations)
    ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg(
                        "SELECT FOR UPDATE/SHARE is not allowed with "
                        "UNION/INTERSECT/EXCEPT")));
  if (qry->distinctClause != NIL)
    ereport(
        ERROR,
        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
         errmsg(
             "SELECT FOR UPDATE/SHARE is not allowed with DISTINCT clause")));
  if (qry->groupClause != NIL)
    ereport(
        ERROR,
        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
         errmsg(
             "SELECT FOR UPDATE/SHARE is not allowed with GROUP BY clause")));
  if (qry->havingQual != NULL)
    ereport(
        ERROR,
        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
         errmsg("SELECT FOR UPDATE/SHARE is not allowed with HAVING clause")));
  if (qry->hasAggs)
    ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg(
                        "SELECT FOR UPDATE/SHARE is not allowed with aggregate "
                        "functions")));
  if (qry->hasWindFuncs)
    ereport(
        ERROR,
        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
         errmsg(
             "SELECT FOR UPDATE/SHARE is not allowed with window functions")));
}

/*
 * Transform a FOR UPDATE/SHARE clause
 *
 * This basically involves replacing names by integer relids.
 *
 * NB: if you need to change this, see also markQueryForLocking()
 * in rewriteHandler.c.
 */
static void transformLockingClause(Query *qry, LockingClause *lc) {
  List *lockedRels = lc->lockedRels;
  ListCell *l;
  ListCell *rt;
  Index i;
  LockingClause *allrels;

  /* disable select for update for gpsql */
  if (lc->forUpdate) {
    ereport(ERROR, (errcode(ERRCODE_CDB_FEATURE_NOT_YET),
                    errmsg("Cannot support select for update statement yet")));
  }

  CheckSelectLocking(qry);

  /* make a clause we can pass down to subqueries to select all rels */
  allrels = makeNode(LockingClause);
  allrels->lockedRels = NIL; /* indicates all rels */
  allrels->forUpdate = lc->forUpdate;
  allrels->noWait = lc->noWait;

  if (lockedRels == NIL) {
    /* all regular tables used in query */
    i = 0;
    foreach (rt, qry->rtable) {
      RangeTblEntry *rte = (RangeTblEntry *)lfirst(rt);

      ++i;
      switch (rte->rtekind) {
        case RTE_RELATION:
          if (get_rel_relstorage(rte->relid) == RELSTORAGE_EXTERNAL)
            ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                            errmsg(
                                "SELECT FOR UPDATE/SHARE cannot be applied to "
                                "external tables")));

          applyLockingClause(qry, i, lc->forUpdate, lc->noWait);
          rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
          break;
        case RTE_SUBQUERY:

          /*
           * FOR UPDATE/SHARE of subquery is propagated to all of
           * subquery's rels
           */
          transformLockingClause(rte->subquery, allrels);
          break;
        default:
          /* ignore JOIN, SPECIAL, FUNCTION RTEs */
          break;
      }
    }
  } else {
    /* just the named tables */
    foreach (l, lockedRels) {
      char *relname = strVal(lfirst(l));

      i = 0;
      foreach (rt, qry->rtable) {
        RangeTblEntry *rte = (RangeTblEntry *)lfirst(rt);

        ++i;
        if (strcmp(rte->eref->aliasname, relname) == 0) {
          switch (rte->rtekind) {
            case RTE_RELATION:
              if (get_rel_relstorage(rte->relid) == RELSTORAGE_EXTERNAL)
                ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                                errmsg(
                                    "SELECT FOR UPDATE/SHARE cannot be applied "
                                    "to external tables")));

              applyLockingClause(qry, i, lc->forUpdate, lc->noWait);
              rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
              break;
            case RTE_SUBQUERY:

              /*
               * FOR UPDATE/SHARE of subquery is propagated to
               * all of subquery's rels
               */
              transformLockingClause(rte->subquery, allrels);
              break;
            case RTE_JOIN:
              ereport(
                  ERROR,
                  (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                   errmsg(
                       "SELECT FOR UPDATE/SHARE cannot be applied to a join")));
              break;
            case RTE_SPECIAL:
              ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                              errmsg(
                                  "SELECT FOR UPDATE/SHARE cannot be applied "
                                  "to NEW or OLD")));
              break;
            case RTE_FUNCTION:
              ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                              errmsg(
                                  "SELECT FOR UPDATE/SHARE cannot be applied "
                                  "to a function")));
              break;
            case RTE_TABLEFUNCTION:
              ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                              errmsg(
                                  "SELECT FOR UPDATE/SHARE cannot be applied "
                                  "to a table function")));
              break;
            case RTE_VALUES:
              ereport(
                  ERROR,
                  (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                   errmsg(
                       "SELECT FOR UPDATE/SHARE cannot be applied to VALUES")));
              break;
            default:
              elog(ERROR, "unrecognized RTE type: %d", (int)rte->rtekind);
              break;
          }
          break; /* out of foreach loop */
        }
      }
      if (rt == NULL)
        ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE),
                        errmsg(
                            "relation \"%s\" in FOR UPDATE/SHARE clause not "
                            "found in FROM clause",
                            relname)));
    }
  }
}

/*
 * Record locking info for a single rangetable item
 */
void applyLockingClause(Query *qry, Index rtindex, bool forUpdate,
                        bool noWait) {
  RowMarkClause *rc;

  /* Check for pre-existing entry for same rtindex */
  if ((rc = get_rowmark(qry, rtindex)) != NULL) {
    /*
     * If the same RTE is specified both FOR UPDATE and FOR SHARE, treat
     * it as FOR UPDATE.  (Reasonable, since you can't take both a shared
     * and exclusive lock at the same time; it'll end up being exclusive
     * anyway.)
     *
     * We also consider that NOWAIT wins if it's specified both ways. This
     * is a bit more debatable but raising an error doesn't seem helpful.
     * (Consider for instance SELECT FOR UPDATE NOWAIT from a view that
     * internally contains a plain FOR UPDATE spec.)
     */
    rc->forUpdate |= forUpdate;
    rc->noWait |= noWait;
    return;
  }

  /* Make a new RowMarkClause */
  rc = makeNode(RowMarkClause);
  rc->rti = rtindex;
  rc->forUpdate = forUpdate;
  rc->noWait = noWait;
  qry->rowMarks = lappend(qry->rowMarks, rc);
}

/*
 * Preprocess a list of column constraint clauses
 * to attach constraint attributes to their primary constraint nodes
 * and detect inconsistent/misplaced constraint attributes.
 *
 * NOTE: currently, attributes are only supported for FOREIGN KEY primary
 * constraints, but someday they ought to be supported for other constraints.
 */
static void transformConstraintAttrs(List *constraintList) {
  Node *lastprimarynode = NULL;
  bool saw_deferrability = false;
  bool saw_initially = false;
  ListCell *clist;

  foreach (clist, constraintList) {
    Node *node = lfirst(clist);

    if (!IsA(node, Constraint)) {
      lastprimarynode = node;
      /* reset flags for new primary node */
      saw_deferrability = false;
      saw_initially = false;
    } else {
      Constraint *con = (Constraint *)node;

      switch (con->contype) {
        case CONSTR_ATTR_DEFERRABLE:
          if (lastprimarynode == NULL || !IsA(lastprimarynode, FkConstraint))
            ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
                            errmsg("misplaced DEFERRABLE clause")));
          if (saw_deferrability)
            ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
                            errmsg(
                                "multiple DEFERRABLE/NOT DEFERRABLE clauses "
                                "not allowed")));
          saw_deferrability = true;
          ((FkConstraint *)lastprimarynode)->deferrable = true;
          break;
        case CONSTR_ATTR_NOT_DEFERRABLE:
          if (lastprimarynode == NULL || !IsA(lastprimarynode, FkConstraint))
            ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
                            errmsg("misplaced NOT DEFERRABLE clause")));
          if (saw_deferrability)
            ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
                            errmsg(
                                "multiple DEFERRABLE/NOT DEFERRABLE clauses "
                                "not allowed")));
          saw_deferrability = true;
          ((FkConstraint *)lastprimarynode)->deferrable = false;
          if (saw_initially && ((FkConstraint *)lastprimarynode)->initdeferred)
            ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
                            errmsg(
                                "constraint declared INITIALLY DEFERRED must "
                                "be DEFERRABLE")));
          break;
        case CONSTR_ATTR_DEFERRED:
          if (lastprimarynode == NULL || !IsA(lastprimarynode, FkConstraint))
            ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
                            errmsg("misplaced INITIALLY DEFERRED clause")));
          if (saw_initially)
            ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
                            errmsg(
                                "multiple INITIALLY IMMEDIATE/DEFERRED clauses "
                                "not allowed")));
          saw_initially = true;
          ((FkConstraint *)lastprimarynode)->initdeferred = true;

          /*
           * If only INITIALLY DEFERRED appears, assume DEFERRABLE
           */
          if (!saw_deferrability)
            ((FkConstraint *)lastprimarynode)->deferrable = true;
          else if (!((FkConstraint *)lastprimarynode)->deferrable)
            ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
                            errmsg(
                                "constraint declared INITIALLY DEFERRED must "
                                "be DEFERRABLE")));
          break;
        case CONSTR_ATTR_IMMEDIATE:
          if (lastprimarynode == NULL || !IsA(lastprimarynode, FkConstraint))
            ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
                            errmsg("misplaced INITIALLY IMMEDIATE clause")));
          if (saw_initially)
            ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
                            errmsg(
                                "multiple INITIALLY IMMEDIATE/DEFERRED clauses "
                                "not allowed")));
          saw_initially = true;
          ((FkConstraint *)lastprimarynode)->initdeferred = false;
          break;
        default:
          /* Otherwise it's not an attribute */
          lastprimarynode = node;
          /* reset flags for new primary node */
          saw_deferrability = false;
          saw_initially = false;
          break;
      }
    }
  }
}

/* Build a FromExpr node */
static FromExpr *makeFromExpr(List *fromlist, Node *quals) {
  FromExpr *f = makeNode(FromExpr);

  f->fromlist = fromlist;
  f->quals = quals;
  return f;
}

/*
 * Special handling of type definition for a column
 */
static void transformColumnType(ParseState *pstate, ColumnDef *column) {
  /*
   * All we really need to do here is verify that the type is valid.
   */
  Type ctype = typenameType(NULL, column->typname);

  ReleaseType(ctype);
}

static void setSchemaName(char *context_schema, char **stmt_schema_name) {
  if (*stmt_schema_name == NULL)
    *stmt_schema_name = context_schema;
  else if (strcmp(context_schema, *stmt_schema_name) != 0)
    ereport(ERROR, (errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
                    errmsg(
                        "CREATE specifies a schema (%s) "
                        "different from the one being created (%s)",
                        *stmt_schema_name, context_schema)));
}

/*
 * analyzeCreateSchemaStmt -
 *	  analyzes the "create schema" statement
 *
 * Split the schema element list into individual commands and place
 * them in the result list in an order such that there are no forward
 * references (e.g. GRANT to a table created later in the list). Note
 * that the logic we use for determining forward references is
 * presently quite incomplete.
 *
 * SQL92 also allows constraints to make forward references, so thumb through
 * the table columns and move forward references to a posterior alter-table
 * command.
 *
 * The result is a list of parse nodes that still need to be analyzed ---
 * but we can't analyze the later commands until we've executed the earlier
 * ones, because of possible inter-object references.
 *
 * Note: Called from commands/schemacmds.c
 */
List *analyzeCreateSchemaStmt(CreateSchemaStmt *stmt) {
  CreateSchemaStmtContext cxt;
  List *result;
  ListCell *elements;

  cxt.stmtType = "CREATE SCHEMA";
  cxt.schemaname = stmt->schemaname;
  cxt.authid = stmt->authid;
  cxt.sequences = NIL;
  cxt.tables = NIL;
  cxt.views = NIL;
  cxt.indexes = NIL;
  cxt.grants = NIL;
  cxt.triggers = NIL;
  cxt.fwconstraints = NIL;
  cxt.alters = NIL;
  cxt.blist = NIL;
  cxt.alist = NIL;

  /*
   * Run through each schema element in the schema element list. Separate
   * statements by type, and do preliminary analysis.
   */
  foreach (elements, stmt->schemaElts) {
    Node *element = lfirst(elements);

    switch (nodeTag(element)) {
      case T_CreateSeqStmt: {
        CreateSeqStmt *elp = (CreateSeqStmt *)element;

        setSchemaName(cxt.schemaname, &elp->sequence->schemaname);
        cxt.sequences = lappend(cxt.sequences, element);
      } break;

      case T_CreateStmt: {
        CreateStmt *elp = (CreateStmt *)element;

        setSchemaName(cxt.schemaname, &elp->base.relation->schemaname);

        /*
         * XXX todo: deal with constraints
         */
        cxt.tables = lappend(cxt.tables, element);
      } break;

      case T_CreateExternalStmt: {
        CreateExternalStmt *elp = (CreateExternalStmt *)element;

        setSchemaName(cxt.schemaname, &elp->base.relation->schemaname);

        cxt.tables = lappend(cxt.tables, element);
      } break;

      case T_CreateForeignStmt: {
        CreateForeignStmt *elp = (CreateForeignStmt *)element;

        setSchemaName(cxt.schemaname, &elp->relation->schemaname);

        cxt.tables = lappend(cxt.tables, element);
      } break;

      case T_ViewStmt: {
        ViewStmt *elp = (ViewStmt *)element;

        setSchemaName(cxt.schemaname, &elp->view->schemaname);

        /*
         * XXX todo: deal with references between views
         */
        cxt.views = lappend(cxt.views, element);
      } break;

      case T_IndexStmt: {
        IndexStmt *elp = (IndexStmt *)element;

        setSchemaName(cxt.schemaname, &elp->relation->schemaname);
        cxt.indexes = lappend(cxt.indexes, element);
      } break;

      case T_CreateTrigStmt: {
        CreateTrigStmt *elp = (CreateTrigStmt *)element;

        setSchemaName(cxt.schemaname, &elp->relation->schemaname);
        cxt.triggers = lappend(cxt.triggers, element);
      } break;

      case T_GrantStmt:
        cxt.grants = lappend(cxt.grants, element);
        break;

      default:
        elog(ERROR, "unrecognized node type: %d", (int)nodeTag(element));
    }
  }

  result = NIL;
  result = list_concat(result, cxt.sequences);
  result = list_concat(result, cxt.tables);
  result = list_concat(result, cxt.views);
  result = list_concat(result, cxt.indexes);
  result = list_concat(result, cxt.triggers);
  result = list_concat(result, cxt.grants);

  return result;
}

/*
 * Traverse a fully-analyzed tree to verify that parameter symbols
 * match their types.  We need this because some Params might still
 * be UNKNOWN, if there wasn't anything to force their coercion,
 * and yet other instances seen later might have gotten coerced.
 */
static bool check_parameter_resolution_walker(
    Node *node, check_parameter_resolution_context *context) {
  if (node == NULL) return false;
  if (IsA(node, Param)) {
    Param *param = (Param *)node;

    if (param->paramkind == PARAM_EXTERN) {
      int paramno = param->paramid;

      if (paramno <= 0 || /* shouldn't happen, but... */
          paramno > context->numParams)
        ereport(ERROR, (errcode(ERRCODE_UNDEFINED_PARAMETER),
                        errmsg("there is no parameter $%d", paramno)));

      if (param->paramtype != context->paramTypes[paramno - 1])
        ereport(ERROR, (errcode(ERRCODE_AMBIGUOUS_PARAMETER),
                        errmsg("could not determine data type of parameter $%d",
                               paramno)));
    }
    return false;
  }
  if (IsA(node, Query)) {
    /* Recurse into RTE subquery or not-yet-planned sublink subquery */
    return query_tree_walker((Query *)node, check_parameter_resolution_walker,
                             (void *)context, 0);
  }
  return expression_tree_walker(node, check_parameter_resolution_walker,
                                (void *)context);
}

static void setQryDistributionPolicy(SelectStmt *stmt, Query *qry) {
  ListCell *keys = NULL;

  GpPolicy *policy = NULL;
  int colindex = 0;
  int maxattrs = 200;

  if (Gp_role != GP_ROLE_DISPATCH) return;

  /*
   * Set default bucketnum for below case:
   * CREATE TABLE ... WITH (bucketnum = ...) AS (SELECT * FROM ...)
   */
  policy = (GpPolicy *)palloc(sizeof(GpPolicy) +
                              maxattrs * sizeof(policy->attrs[0]));
  policy->ptype = POLICYTYPE_PARTITIONED;
  policy->nattrs = 0;
  policy->attrs[0] = 1;
  if (stmt->intoClause != NULL)
    policy->bucketnum = GetRelOpt_bucket_num_fromOptions(
        stmt->intoClause->options, GetDefaultPartitionNum());

  if (stmt->distributedBy) {
    /*
     * We have a DISTRIBUTED BY column list specified by the user
     * Process it now and set the distribution policy.
     */

    if (stmt->distributedBy->length != 1 ||
        (list_head(stmt->distributedBy) != NULL &&
         linitial(stmt->distributedBy) != NULL)) {
      foreach (keys, stmt->distributedBy) {
        char *key = strVal(lfirst(keys));
        bool found = false;

        AttrNumber n;

        for (n = 1; n <= list_length(qry->targetList); n++) {
          TargetEntry *target = get_tle_by_resno(qry->targetList, n);
          colindex = n;

          if (target->resname && strcmp(target->resname, key) == 0) {
            found = true;

          } /*if*/

          if (found) break;

        } /*for*/

        if (!found)
          ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN),
                          errmsg(
                              "column \"%s\" named in DISTRIBUTED BY "
                              "clause does not exist",
                              key),
                          errOmitLocation(true)));

        policy->attrs[policy->nattrs++] = colindex;

      } /*foreach */
    }

    List *options = stmt->intoClause != NULL ? stmt->intoClause->options : NULL;
    if (policy->nattrs > 0) {
      policy->bucketnum =
          GetRelOpt_bucket_num_fromOptions(options, GetHashDistPartitionNum());
    } else {
      policy->bucketnum =
          GetRelOpt_bucket_num_fromOptions(options, GetDefaultPartitionNum());
    }
  }

  qry->intoPolicy = policy;
}

/*
 * getLikeDistributionPolicy
 *
 * For Greenplum Database distributed tables, default to
 * the same distribution as the first LIKE table, unless
 * we also have INHERITS
 */
static List *getLikeDistributionPolicy(InhRelation *e) {
  List *likeDistributedBy = NIL;
  Oid relId;
  GpPolicy *oldTablePolicy;

  relId = RangeVarGetRelid(e->relation, false, true /*allowHcatalog*/);
  oldTablePolicy = GpPolicyFetch(CurrentMemoryContext, relId);

  if (oldTablePolicy != NULL &&
      oldTablePolicy->ptype == POLICYTYPE_PARTITIONED) {
    int ia;

    if (oldTablePolicy->nattrs > 0) {
      for (ia = 0; ia < oldTablePolicy->nattrs; ia++) {
        char *attname = get_attname(relId, oldTablePolicy->attrs[ia]);

        if (likeDistributedBy)
          likeDistributedBy =
              lappend(likeDistributedBy, (Node *)makeString(attname));
        else
          likeDistributedBy = list_make1((Node *)makeString(attname));
      }
    } else { /* old table is distributed randomly. */
      likeDistributedBy = list_make1((Node *)NULL);
    }
  }

  return likeDistributedBy;
}

/*
 * transformSingleRowErrorHandling
 *
 * If Single row error handling was specified with an error table, we first
 * check if a table with the same name already exists. If yes, we verify that
 * it has the correct pre-defined error table metadata format (and throw an
 * error is it doesn't). If such a table doesn't exist, we add a CREATE TABLE
 * statement to the before list of the CREATE EXTERNAL TABLE so that it will
 * be created before the external table.
 */
static void transformSingleRowErrorHandling(ParseState *pstate,
                                            CreateStmtContext *cxt,
                                            SingleRowErrorDesc *sreh) {
  /* check if error relation exists already */
  Oid errreloid =
      RangeVarGetRelid(sreh->errtable, true, false /*allowHcatalog*/);
  bool errrelexists = OidIsValid(errreloid);

  /*
   * auto-generate a new error table or verify an existing one
   */
  if (errrelexists) {
    /* disable for hdfs protocol text error table */
    if (sreh->is_hdfs_protocol_text)
      ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                      errmsg(
                          "Error table for hdfs protocol text/csv can't reuse "
                          "existing table"),
                      errOmitLocation(true)));
    /*
     * this table already exists in the database. Verify that specified
     * table is a valid error table
     */

    Relation rel;

    rel = heap_openrv(sreh->errtable, AccessShareLock);
    ValidateErrorTableMetaData(rel);
    relation_close(rel, AccessShareLock);

    sreh->reusing_existing_errtable = true;
  } else {
    /*
     * no such table. auto create it by adding a CREATE TABLE <errortable>
     * to the before list
     */
    List *attrList;
    int i = 0;

    ereport(NOTICE, (errmsg(
                        "Error table \"%s\" does not exist. Auto generating an "
                        "error table with the same name",
                        sreh->errtable->relname)));

    /*
     * create a list of ColumnDef nodes based on the names and types of the
     * per-defined error table columns
     */
    attrList = NIL;

    for (i = 1; i <= NUM_ERRORTABLE_ATTR; i++) {
      ColumnDef *coldef = makeNode(ColumnDef);

      coldef->inhcount = 0;
      coldef->is_local = true;
      coldef->is_not_null = false;
      coldef->raw_default = NULL;
      coldef->cooked_default = NULL;
      coldef->constraints = NIL;

      switch (i) {
        case errtable_cmdtime:
          coldef->typname = makeTypeNameFromOid(TIMESTAMPTZOID, -1);
          coldef->colname = "cmdtime";
          break;
        case errtable_relname:
          coldef->typname = makeTypeNameFromOid(TEXTOID, -1);
          coldef->colname = "relname";
          break;
        case errtable_filename:
          coldef->typname = makeTypeNameFromOid(TEXTOID, -1);
          coldef->colname = "filename";
          break;
        case errtable_linenum:
          coldef->typname = makeTypeNameFromOid(INT4OID, -1);
          coldef->colname = "linenum";
          break;
        case errtable_bytenum:
          coldef->typname = makeTypeNameFromOid(INT4OID, -1);
          coldef->colname = "bytenum";
          break;
        case errtable_errmsg:
          coldef->typname = makeTypeNameFromOid(TEXTOID, -1);
          coldef->colname = "errmsg";
          break;
        case errtable_rawdata:
          coldef->typname = makeTypeNameFromOid(TEXTOID, -1);
          coldef->colname = "rawdata";
          break;
        case errtable_rawbytes:
          coldef->typname = makeTypeNameFromOid(BYTEAOID, -1);
          coldef->colname = "rawbytes";
          break;
      }
      attrList = lappend(attrList, coldef);
    }

    if (!sreh->is_hdfs_protocol_text) {
      CreateStmt *createStmt = makeNode(CreateStmt);
      createStmt->base.relation = sreh->errtable;
      createStmt->base.tableElts = attrList;
      createStmt->base.inhRelations = NIL;
      createStmt->base.constraints = NIL;
      createStmt->base.options =
          list_make2(makeDefElem("errortable", (Node *)makeString("true")),
                     makeDefElem("appendonly", (Node *)makeString("true")));
      createStmt->base.oncommit = ONCOMMIT_NOOP;
      createStmt->base.tablespacename = NULL;
      createStmt->base.relKind = RELKIND_RELATION;
      createStmt->relStorage = RELSTORAGE_AOROWS;
      createStmt->base.distributedBy =
          list_make1(NULL); /* DISTRIBUTED RANDOMLY */
      cxt->blist = lappend(cxt->blist, createStmt);
    } else {
      CreateExternalStmt *createExternalStmt = makeNode(CreateExternalStmt);
      createExternalStmt->base.relation = sreh->errtable;
      createExternalStmt->isexternal = true;
      createExternalStmt->base.tableElts = attrList;
      createExternalStmt->base.distributedBy = NULL;
      createExternalStmt->sreh = NULL;
      createExternalStmt->policy = NULL;
      createExternalStmt->isweb = false;
      createExternalStmt->iswritable = false;
      createExternalStmt->format = pstrdup("csv");
      createExternalStmt->forceCreateDir = true;
      ExtTableTypeDesc *desc = makeNode(ExtTableTypeDesc);
      desc->exttabletype = EXTTBL_TYPE_LOCATION;
      desc->on_clause = NIL;
      desc->command_string = NULL;
      desc->location_list = list_make1(makeString(pstrdup(sreh->hdfsLoc)));
      createExternalStmt->exttypedesc = desc;
      createExternalStmt->base.options = NULL;
      createExternalStmt->encoding = list_make1(makeDefElem(
          "encoding",
          (Node *)makeString(pg_encoding_to_char(pg_get_client_encoding()))));
      cxt->blist = lappend(cxt->blist, createExternalStmt);
    }

    sreh->reusing_existing_errtable = false;
  }
}

/*
 * create a policy with random distribution
 */
GpPolicy *createRandomDistribution(int maxattrs) {
  GpPolicy *p = NULL;
  p = (GpPolicy *)palloc(sizeof(GpPolicy) + maxattrs * sizeof(p->attrs[0]));
  p->ptype = POLICYTYPE_PARTITIONED;
  p->nattrs = 0;
  p->attrs[0] = 1;

  return p;
}

/*
 * get default filespace path for hdfs, data of hawq is stored in hdfs and magma,
 * there is only hdfs protocol in need of filespace path.
 */
char *getDefaultFilespace() {
	//need to free
    char *ret = (char *)palloc0(MAXPGPATH);
    sprintf(ret, "%s%s", PROTOCOL_HDFS, dfs_url);
	return ret;
}
